summaryrefslogtreecommitdiff
path: root/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs
diff options
context:
space:
mode:
Diffstat (limited to 'ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs')
-rw-r--r--ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs543
1 files changed, 543 insertions, 0 deletions
diff --git a/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs b/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs
new file mode 100644
index 00000000..4a4a8138
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs
@@ -0,0 +1,543 @@
+// 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>
+ /// IL AST transformation that introduces array, object and collection initializers.
+ /// </summary>
+ partial class ILAstOptimizer
+ {
+ #region Array Initializers
+ bool TransformArrayInitializers(List<ILNode> body, ILExpression expr, int pos)
+ {
+ ILVariable v, v3;
+ ILExpression newarrExpr;
+ TypeReference elementType;
+ ILExpression lengthExpr;
+ int arrayLength;
+ if (expr.Match(ILCode.Stloc, out v, out newarrExpr) &&
+ newarrExpr.Match(ILCode.Newarr, out elementType, out lengthExpr) &&
+ lengthExpr.Match(ILCode.Ldc_I4, out arrayLength) &&
+ arrayLength > 0) {
+ ILExpression[] newArr;
+ int initArrayPos;
+ if (ForwardScanInitializeArrayRuntimeHelper(body, pos + 1, v, elementType, arrayLength, out newArr, out initArrayPos)) {
+ var arrayType = new ArrayType(elementType, 1);
+ arrayType.Dimensions[0] = new ArrayDimension(0, arrayLength);
+ body[pos] = new ILExpression(ILCode.Stloc, v, new ILExpression(ILCode.InitArray, arrayType, newArr));
+ body.RemoveAt(initArrayPos);
+ }
+ // Put in a limit so that we don't consume too much memory if the code allocates a huge array
+ // and populates it extremely sparsly. However, 255 "null" elements in a row actually occur in the Mono C# compiler!
+ const int maxConsecutiveDefaultValueExpressions = 300;
+ List<ILExpression> operands = new List<ILExpression>();
+ int numberOfInstructionsToRemove = 0;
+ for (int j = pos + 1; j < body.Count; j++) {
+ ILExpression nextExpr = body[j] as ILExpression;
+ int arrayPos;
+ if (nextExpr != null &&
+ nextExpr.Code.IsStoreToArray() &&
+ nextExpr.Arguments[0].Match(ILCode.Ldloc, out v3) &&
+ v == v3 &&
+ nextExpr.Arguments[1].Match(ILCode.Ldc_I4, out arrayPos) &&
+ arrayPos >= operands.Count &&
+ arrayPos <= operands.Count + maxConsecutiveDefaultValueExpressions &&
+ !nextExpr.Arguments[2].ContainsReferenceTo(v3))
+ {
+ while (operands.Count < arrayPos)
+ operands.Add(new ILExpression(ILCode.DefaultValue, elementType));
+ operands.Add(nextExpr.Arguments[2]);
+ numberOfInstructionsToRemove++;
+ } else {
+ break;
+ }
+ }
+ if (operands.Count == arrayLength) {
+ var arrayType = new ArrayType(elementType, 1);
+ arrayType.Dimensions[0] = new ArrayDimension(0, arrayLength);
+ expr.Arguments[0] = new ILExpression(ILCode.InitArray, arrayType, operands);
+ body.RemoveRange(pos + 1, numberOfInstructionsToRemove);
+
+ new ILInlining(method).InlineIfPossible(body, ref pos);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool TransformMultidimensionalArrayInitializers(List<ILNode> body, ILExpression expr, int pos)
+ {
+ ILVariable v;
+ ILExpression newarrExpr;
+ MethodReference ctor;
+ List<ILExpression> ctorArgs;
+ ArrayType arrayType;
+ if (expr.Match(ILCode.Stloc, out v, out newarrExpr) &&
+ newarrExpr.Match(ILCode.Newobj, out ctor, out ctorArgs) &&
+ (arrayType = (ctor.DeclaringType as ArrayType)) != null &&
+ arrayType.Rank == ctorArgs.Count) {
+ // Clone the type, so we can muck about with the Dimensions
+ arrayType = new ArrayType(arrayType.ElementType, arrayType.Rank);
+ var arrayLengths = new int[arrayType.Rank];
+ for (int i = 0; i < arrayType.Rank; i++) {
+ if (!ctorArgs[i].Match(ILCode.Ldc_I4, out arrayLengths[i])) return false;
+ if (arrayLengths[i] <= 0) return false;
+ arrayType.Dimensions[i] = new ArrayDimension(0, arrayLengths[i]);
+ }
+
+ var totalElements = arrayLengths.Aggregate(1, (t, l) => t * l);
+ ILExpression[] newArr;
+ int initArrayPos;
+ if (ForwardScanInitializeArrayRuntimeHelper(body, pos + 1, v, arrayType, totalElements, out newArr, out initArrayPos)) {
+ body[pos] = new ILExpression(ILCode.Stloc, v, new ILExpression(ILCode.InitArray, arrayType, newArr));
+ body.RemoveAt(initArrayPos);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool ForwardScanInitializeArrayRuntimeHelper(List<ILNode> body, int pos, ILVariable array, TypeReference arrayType, int arrayLength, out ILExpression[] values, out int foundPos)
+ {
+ ILVariable v2;
+ MethodReference methodRef;
+ ILExpression methodArg1;
+ ILExpression methodArg2;
+ FieldReference fieldRef;
+ if (body.ElementAtOrDefault(pos).Match(ILCode.Call, out methodRef, out methodArg1, out methodArg2) &&
+ methodRef.DeclaringType.FullName == "System.Runtime.CompilerServices.RuntimeHelpers" &&
+ methodRef.Name == "InitializeArray" &&
+ methodArg1.Match(ILCode.Ldloc, out v2) &&
+ array == v2 &&
+ methodArg2.Match(ILCode.Ldtoken, out fieldRef))
+ {
+ FieldDefinition fieldDef = fieldRef.ResolveWithinSameModule();
+ if (fieldDef != null && fieldDef.InitialValue != null) {
+ ILExpression[] newArr = new ILExpression[arrayLength];
+ if (DecodeArrayInitializer(arrayType.GetElementType(), fieldDef.InitialValue, newArr))
+ {
+ values = newArr;
+ foundPos = pos;
+ return true;
+ }
+ }
+ }
+ values = null;
+ foundPos = -1;
+ return false;
+ }
+
+ static bool DecodeArrayInitializer(TypeReference elementTypeRef, byte[] initialValue, ILExpression[] output)
+ {
+ TypeCode elementType = TypeAnalysis.GetTypeCode(elementTypeRef);
+ switch (elementType) {
+ case TypeCode.Boolean:
+ case TypeCode.Byte:
+ return DecodeArrayInitializer(initialValue, output, elementType, (d, i) => (int)d[i]);
+ case TypeCode.SByte:
+ return DecodeArrayInitializer(initialValue, output, elementType, (d, i) => (int)unchecked((sbyte)d[i]));
+ case TypeCode.Int16:
+ return DecodeArrayInitializer(initialValue, output, elementType, (d, i) => (int)BitConverter.ToInt16(d, i));
+ case TypeCode.Char:
+ case TypeCode.UInt16:
+ return DecodeArrayInitializer(initialValue, output, elementType, (d, i) => (int)BitConverter.ToUInt16(d, i));
+ case TypeCode.Int32:
+ case TypeCode.UInt32:
+ return DecodeArrayInitializer(initialValue, output, elementType, BitConverter.ToInt32);
+ case TypeCode.Int64:
+ case TypeCode.UInt64:
+ return DecodeArrayInitializer(initialValue, output, elementType, BitConverter.ToInt64);
+ case TypeCode.Single:
+ return DecodeArrayInitializer(initialValue, output, elementType, BitConverter.ToSingle);
+ case TypeCode.Double:
+ return DecodeArrayInitializer(initialValue, output, elementType, BitConverter.ToDouble);
+ case TypeCode.Object:
+ var typeDef = elementTypeRef.ResolveWithinSameModule();
+ if (typeDef != null && typeDef.IsEnum)
+ return DecodeArrayInitializer(typeDef.GetEnumUnderlyingType(), initialValue, output);
+
+ return false;
+ default:
+ return false;
+ }
+ }
+
+ static bool DecodeArrayInitializer<T>(byte[] initialValue, ILExpression[] output, TypeCode elementType, Func<byte[], int, T> decoder)
+ {
+ int elementSize = ElementSizeOf(elementType);
+ if (initialValue.Length < (output.Length * elementSize))
+ return false;
+
+ ILCode code = LoadCodeFor(elementType);
+ for (int i = 0; i < output.Length; i++)
+ output[i] = new ILExpression(code, decoder(initialValue, i * elementSize));
+
+ return true;
+ }
+
+ static ILCode LoadCodeFor(TypeCode elementType)
+ {
+ switch (elementType) {
+ case TypeCode.Boolean:
+ case TypeCode.Byte:
+ case TypeCode.SByte:
+ case TypeCode.Char:
+ case TypeCode.Int16:
+ case TypeCode.UInt16:
+ case TypeCode.Int32:
+ case TypeCode.UInt32:
+ return ILCode.Ldc_I4;
+ case TypeCode.Int64:
+ case TypeCode.UInt64:
+ return ILCode.Ldc_I8;
+ case TypeCode.Single:
+ return ILCode.Ldc_R4;
+ case TypeCode.Double:
+ return ILCode.Ldc_R8;
+ default:
+ throw new ArgumentOutOfRangeException("elementType");
+ }
+ }
+
+ static int ElementSizeOf(TypeCode elementType)
+ {
+ switch (elementType) {
+ case TypeCode.Boolean:
+ case TypeCode.Byte:
+ case TypeCode.SByte:
+ return 1;
+ case TypeCode.Char:
+ case TypeCode.Int16:
+ case TypeCode.UInt16:
+ return 2;
+ case TypeCode.Int32:
+ case TypeCode.UInt32:
+ case TypeCode.Single:
+ return 4;
+ case TypeCode.Int64:
+ case TypeCode.UInt64:
+ case TypeCode.Double:
+ return 8;
+ default:
+ throw new ArgumentOutOfRangeException("elementType");
+ }
+ }
+ #endregion
+
+ /// <summary>
+ /// Handles both object and collection initializers.
+ /// </summary>
+ bool TransformObjectInitializers(List<ILNode> body, ILExpression expr, int pos)
+ {
+ if (!context.Settings.ObjectOrCollectionInitializers)
+ return false;
+
+ Debug.Assert(body[pos] == expr); // should be called for top-level expressions only
+ ILVariable v;
+ ILExpression newObjExpr;
+ TypeReference newObjType;
+ bool isValueType;
+ MethodReference ctor;
+ List<ILExpression> ctorArgs;
+ if (expr.Match(ILCode.Stloc, out v, out newObjExpr)) {
+ if (newObjExpr.Match(ILCode.Newobj, out ctor, out ctorArgs)) {
+ // v = newObj(ctor, ctorArgs)
+ newObjType = ctor.DeclaringType;
+ isValueType = false;
+ } else if (newObjExpr.Match(ILCode.DefaultValue, out newObjType)) {
+ // v = defaultvalue(type)
+ isValueType = true;
+ } else {
+ return false;
+ }
+ } else if (expr.Match(ILCode.Call, out ctor, out ctorArgs)) {
+ // call(SomeStruct::.ctor, ldloca(v), remainingArgs)
+ if (ctorArgs.Count > 0 && ctorArgs[0].Match(ILCode.Ldloca, out v)) {
+ isValueType = true;
+ newObjType = ctor.DeclaringType;
+ ctorArgs = new List<ILExpression>(ctorArgs);
+ ctorArgs.RemoveAt(0);
+ newObjExpr = new ILExpression(ILCode.Newobj, ctor, ctorArgs);
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ if (newObjType.IsValueType != isValueType)
+ return false;
+
+ int originalPos = pos;
+
+ // don't use object initializer syntax for closures
+ if (Ast.Transforms.DelegateConstruction.IsPotentialClosure(context, newObjType.ResolveWithinSameModule()))
+ return false;
+
+ ILExpression initializer = ParseObjectInitializer(body, ref pos, v, newObjExpr, IsCollectionType(newObjType), isValueType);
+
+ if (initializer.Arguments.Count == 1) // only newobj argument, no initializer elements
+ return false;
+ int totalElementCount = pos - originalPos - 1; // totalElementCount: includes elements from nested collections
+ Debug.Assert(totalElementCount >= initializer.Arguments.Count - 1);
+
+ // Verify that we can inline 'v' into the next instruction:
+
+ if (pos >= body.Count)
+ return false; // reached end of block, but there should be another instruction which consumes the initialized object
+
+ ILInlining inlining = new ILInlining(method);
+ if (isValueType) {
+ // one ldloc for the use of the initialized object
+ if (inlining.numLdloc.GetOrDefault(v) != 1)
+ return false;
+ // one ldloca for each initializer argument, and also for the ctor call (if it exists)
+ if (inlining.numLdloca.GetOrDefault(v) != totalElementCount + (expr.Code == ILCode.Call ? 1 : 0))
+ return false;
+ // one stloc for the initial store (if no ctor call was used)
+ if (inlining.numStloc.GetOrDefault(v) != (expr.Code == ILCode.Call ? 0 : 1))
+ return false;
+ } else {
+ // one ldloc for each initializer argument, and another ldloc for the use of the initialized object
+ if (inlining.numLdloc.GetOrDefault(v) != totalElementCount + 1)
+ return false;
+ if (!(inlining.numStloc.GetOrDefault(v) == 1 && inlining.numLdloca.GetOrDefault(v) == 0))
+ return false;
+ }
+ ILExpression nextExpr = body[pos] as ILExpression;
+ if (!inlining.CanInlineInto(nextExpr, v, initializer))
+ return false;
+
+ if (expr.Code == ILCode.Stloc) {
+ expr.Arguments[0] = initializer;
+ } else {
+ Debug.Assert(expr.Code == ILCode.Call);
+ expr.Code = ILCode.Stloc;
+ expr.Operand = v;
+ expr.Arguments.Clear();
+ expr.Arguments.Add(initializer);
+ }
+ // remove all the instructions that were pulled into the initializer
+ body.RemoveRange(originalPos + 1, pos - originalPos - 1);
+
+ // now that we know that it's an object initializer, change all the first arguments to 'InitializedObject'
+ ChangeFirstArgumentToInitializedObject(initializer);
+
+ inlining = new ILInlining(method);
+ inlining.InlineIfPossible(body, ref originalPos);
+
+ return true;
+ }
+
+ /// <summary>
+ /// Gets whether the type supports collection initializers.
+ /// </summary>
+ static bool IsCollectionType(TypeReference tr)
+ {
+ if (tr == null)
+ return false;
+ TypeDefinition td = tr.Resolve();
+ while (td != null) {
+ if (td.Interfaces.Any(intf => intf.Name == "IEnumerable" && intf.Namespace == "System.Collections"))
+ return true;
+ td = td.BaseType != null ? td.BaseType.Resolve() : null;
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Gets whether 'expr' represents a setter in an object initializer.
+ /// ('CallvirtSetter(Property, v, value)')
+ /// </summary>
+ static bool IsSetterInObjectInitializer(ILExpression expr)
+ {
+ if (expr == null)
+ return false;
+ if (expr.Code == ILCode.CallvirtSetter || expr.Code == ILCode.CallSetter || expr.Code == ILCode.Stfld) {
+ return expr.Arguments.Count == 2;
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Gets whether 'expr' represents the invocation of an 'Add' method in a collection initializer.
+ /// </summary>
+ static bool IsAddMethodCall(ILExpression expr)
+ {
+ MethodReference addMethod;
+ List<ILExpression> args;
+ if (expr.Match(ILCode.Callvirt, out addMethod, out args) || expr.Match(ILCode.Call, out addMethod, out args)) {
+ if (addMethod.Name == "Add" && addMethod.HasThis) {
+ return args.Count >= 2;
+ }
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Parses an object initializer.
+ /// </summary>
+ /// <param name="body">ILAst block</param>
+ /// <param name="pos">
+ /// Input: position of the instruction assigning to 'v'.
+ /// Output: first position after the object initializer
+ /// </param>
+ /// <param name="v">The variable that holds the object being initialized</param>
+ /// <param name="newObjExpr">The newobj instruction</param>
+ /// <returns>InitObject instruction</returns>
+ ILExpression ParseObjectInitializer(List<ILNode> body, ref int pos, ILVariable v, ILExpression newObjExpr, bool isCollection, bool isValueType)
+ {
+ // Take care not to modify any existing ILExpressions in here.
+ // We just construct new ones around the old ones, any modifications must wait until the whole
+ // object/collection initializer was analyzed.
+ ILExpression objectInitializer = new ILExpression(isCollection ? ILCode.InitCollection : ILCode.InitObject, null, newObjExpr);
+ List<ILExpression> initializerStack = new List<ILExpression>();
+ initializerStack.Add(objectInitializer);
+ while (++pos < body.Count) {
+ ILExpression nextExpr = body[pos] as ILExpression;
+ if (IsSetterInObjectInitializer(nextExpr)) {
+ if (!AdjustInitializerStack(initializerStack, nextExpr.Arguments[0], v, false, isValueType)) {
+ CleanupInitializerStackAfterFailedAdjustment(initializerStack);
+ break;
+ }
+ initializerStack[initializerStack.Count - 1].Arguments.Add(nextExpr);
+ } else if (IsAddMethodCall(nextExpr)) {
+ if (!AdjustInitializerStack(initializerStack, nextExpr.Arguments[0], v, true, isValueType)) {
+ CleanupInitializerStackAfterFailedAdjustment(initializerStack);
+ break;
+ }
+ initializerStack[initializerStack.Count - 1].Arguments.Add(nextExpr);
+ } else {
+ // can't match any more initializers: end of object initializer
+ break;
+ }
+ }
+ return objectInitializer;
+ }
+
+ static bool AdjustInitializerStack(List<ILExpression> initializerStack, ILExpression argument, ILVariable v, bool isCollection, bool isValueType)
+ {
+ // Argument is of the form 'getter(getter(...(v)))'
+ // Unpack it into a list of getters:
+ List<ILExpression> getters = new List<ILExpression>();
+ while (argument.Code == ILCode.CallvirtGetter || argument.Code == ILCode.CallGetter || argument.Code == ILCode.Ldfld) {
+ getters.Add(argument);
+ if (argument.Arguments.Count != 1)
+ return false;
+ argument = argument.Arguments[0];
+ }
+ // Ensure that the final argument is 'v'
+ if (isValueType) {
+ ILVariable loadedVar;
+ if (!(argument.Match(ILCode.Ldloca, out loadedVar) && loadedVar == v))
+ return false;
+ } else {
+ if (!argument.MatchLdloc(v))
+ return false;
+ }
+ // Now compare the getters with those that are currently active on the initializer stack:
+ int i;
+ for (i = 1; i <= Math.Min(getters.Count, initializerStack.Count - 1); i++) {
+ ILExpression g1 = initializerStack[i].Arguments[0]; // getter stored in initializer
+ ILExpression g2 = getters[getters.Count - i]; // matching getter from argument
+ if (g1.Operand != g2.Operand) {
+ // operands differ, so we abort the comparison
+ break;
+ }
+ }
+ // Remove all initializers from the stack that were not matched with one from the argument:
+ initializerStack.RemoveRange(i, initializerStack.Count - i);
+ // Now create new initializers for the remaining arguments:
+ for (; i <= getters.Count; i++) {
+ ILExpression g = getters[getters.Count - i];
+ MemberReference mr = (MemberReference)g.Operand;
+ TypeReference returnType;
+ if (mr is FieldReference)
+ returnType = TypeAnalysis.GetFieldType((FieldReference)mr);
+ else
+ returnType = TypeAnalysis.SubstituteTypeArgs(((MethodReference)mr).ReturnType, mr);
+
+ ILExpression nestedInitializer = new ILExpression(
+ IsCollectionType(returnType) ? ILCode.InitCollection : ILCode.InitObject,
+ null, g);
+ // add new initializer to its parent:
+ ILExpression parentInitializer = initializerStack[initializerStack.Count - 1];
+ if (parentInitializer.Code == ILCode.InitCollection) {
+ // can't add children to collection initializer
+ if (parentInitializer.Arguments.Count == 1) {
+ // convert empty collection initializer to object initializer
+ parentInitializer.Code = ILCode.InitObject;
+ } else {
+ return false;
+ }
+ }
+ parentInitializer.Arguments.Add(nestedInitializer);
+ initializerStack.Add(nestedInitializer);
+ }
+ ILExpression lastInitializer = initializerStack[initializerStack.Count - 1];
+ if (isCollection) {
+ return lastInitializer.Code == ILCode.InitCollection;
+ } else {
+ if (lastInitializer.Code == ILCode.InitCollection) {
+ if (lastInitializer.Arguments.Count == 1) {
+ // convert empty collection initializer to object initializer
+ lastInitializer.Code = ILCode.InitObject;
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return true;
+ }
+ }
+ }
+
+ static void CleanupInitializerStackAfterFailedAdjustment(List<ILExpression> initializerStack)
+ {
+ // There might be empty nested initializers left over; so we'll remove those:
+ while (initializerStack.Count > 1 && initializerStack[initializerStack.Count - 1].Arguments.Count == 1) {
+ ILExpression parent = initializerStack[initializerStack.Count - 2];
+ Debug.Assert(parent.Arguments.Last() == initializerStack[initializerStack.Count - 1]);
+ parent.Arguments.RemoveAt(parent.Arguments.Count - 1);
+ initializerStack.RemoveAt(initializerStack.Count - 1);
+ }
+ }
+
+ static void ChangeFirstArgumentToInitializedObject(ILExpression initializer)
+ {
+ // Go through all elements in the initializer (so skip the newobj-instr. at the start)
+ for (int i = 1; i < initializer.Arguments.Count; i++) {
+ ILExpression element = initializer.Arguments[i];
+ if (element.Code == ILCode.InitCollection || element.Code == ILCode.InitObject) {
+ // nested collection/object initializer
+ ILExpression getCollection = element.Arguments[0];
+ getCollection.Arguments[0] = new ILExpression(ILCode.InitializedObject, null);
+ ChangeFirstArgumentToInitializedObject(element); // handle the collection elements
+ } else {
+ element.Arguments[0] = new ILExpression(ILCode.InitializedObject, null);
+ }
+ }
+ }
+ }
+}