Browse Source

- Modified the InitializerPeepholeTransforms' Array Initializers to do a forward scan of the block instead of just checking the next instruction. The next-instruction thing breaks down under the case where you have an array-of-arrays (int[][])

- Added to the InitializerPeepholeTransforms' Array Initializers to detect the creation of a multi-dimensional array (int[,])
- Modified the ILCode.InitArray contract to take an ArrayType instead of just the element type, and passing with the ArrayType.Dimensions set accordingly.
- AstMethodBodyBuilder now used the ArrayType.Dimensions info to build a tree of ArrayInitializerExpressions from the raw, element-by-element list.
- Fixed OutputVisitor not calling StartNode for EmptyExpressions

Known issues:
- ArrayCreateExpression outputs extra space in the array specifier when using EmptyExpressions, ala: "new int[][, ]"
- The tree of ArrayInitializerExpressions outputs with blank lines before and after each block.
pull/252/head
Alex Lyman 14 years ago committed by Daniel Grunwald
parent
commit
9faee0ad8d
  1. 37
      ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs
  2. 4
      ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs
  3. 144
      ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs
  4. 8
      ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs
  5. 253
      ICSharpCode.Decompiler/Tests/InitializerTests.cs
  6. 1
      NRefactory/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs

37
ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs

@ -349,8 +349,7 @@ namespace ICSharpCode.Decompiler.Ast
} }
#endregion #endregion
#region Arrays #region Arrays
case ILCode.Newarr: case ILCode.Newarr: {
case ILCode.InitArray: {
var ace = new Ast.ArrayCreateExpression(); var ace = new Ast.ArrayCreateExpression();
ace.Type = operandAsTypeRef; ace.Type = operandAsTypeRef;
ComposedType ct = operandAsTypeRef as ComposedType; ComposedType ct = operandAsTypeRef as ComposedType;
@ -366,7 +365,39 @@ namespace ICSharpCode.Decompiler.Ast
} }
return ace; return ace;
} }
case ILCode.Ldlen: return arg1.Member("Length"); case ILCode.InitArray: {
var ace = new Ast.ArrayCreateExpression();
ace.Type = operandAsTypeRef;
ComposedType ct = operandAsTypeRef as ComposedType;
var arrayType = (ArrayType) operand;
if (ct != null)
{
// change "new (int[,])[10] to new int[10][,]"
ct.ArraySpecifiers.MoveTo(ace.AdditionalArraySpecifiers);
ace.Initializer = new ArrayInitializerExpression();
var first = ace.AdditionalArraySpecifiers.First();
first.Remove();
ace.Arguments.AddRange(Enumerable.Repeat(0, first.Dimensions).Select(i => new EmptyExpression()));
}
var newArgs = new List<Expression>();
foreach (var arrayDimension in arrayType.Dimensions.Skip(1).Reverse())
{
int length = (int)arrayDimension.UpperBound - (int)arrayDimension.LowerBound;
for (int j = 0; j < args.Count; j += length)
{
var child = new ArrayInitializerExpression();
child.Elements.AddRange(args.GetRange(j, length));
newArgs.Add(child);
}
var temp = args;
args = newArgs;
newArgs = temp;
newArgs.Clear();
}
ace.Initializer.Elements.AddRange(args);
return ace;
}
case ILCode.Ldlen: return arg1.Member("Length");
case ILCode.Ldelem_I: case ILCode.Ldelem_I:
case ILCode.Ldelem_I1: case ILCode.Ldelem_I1:
case ILCode.Ldelem_I2: case ILCode.Ldelem_I2:

4
ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs

@ -46,6 +46,7 @@ namespace ICSharpCode.Decompiler.ILAst
SimplifyLdObjAndStObj, SimplifyLdObjAndStObj,
SimplifyCustomShortCircuit, SimplifyCustomShortCircuit,
TransformArrayInitializers, TransformArrayInitializers,
TransformMultidimensionalArrayInitializers,
TransformObjectInitializers, TransformObjectInitializers,
MakeAssignmentExpression, MakeAssignmentExpression,
IntroducePostIncrement, IntroducePostIncrement,
@ -143,6 +144,9 @@ namespace ICSharpCode.Decompiler.ILAst
if (abortBeforeStep == ILAstOptimizationStep.TransformArrayInitializers) return; if (abortBeforeStep == ILAstOptimizationStep.TransformArrayInitializers) return;
modified |= block.RunOptimization(TransformArrayInitializers); modified |= block.RunOptimization(TransformArrayInitializers);
if (abortBeforeStep == ILAstOptimizationStep.TransformMultidimensionalArrayInitializers) return;
modified |= block.RunOptimization(TransformMultidimensionalArrayInitializers);
if (abortBeforeStep == ILAstOptimizationStep.TransformObjectInitializers) return; if (abortBeforeStep == ILAstOptimizationStep.TransformObjectInitializers) return;
modified |= block.RunOptimization(TransformObjectInitializers); modified |= block.RunOptimization(TransformObjectInitializers);

144
ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs

@ -34,35 +34,21 @@ namespace ICSharpCode.Decompiler.ILAst
{ {
ILVariable v, v2, v3; ILVariable v, v2, v3;
ILExpression newarrExpr; ILExpression newarrExpr;
TypeReference arrayType; TypeReference elementType;
ILExpression lengthExpr; ILExpression lengthExpr;
int arrayLength; int arrayLength;
if (expr.Match(ILCode.Stloc, out v, out newarrExpr) && if (expr.Match(ILCode.Stloc, out v, out newarrExpr) &&
newarrExpr.Match(ILCode.Newarr, out arrayType, out lengthExpr) && newarrExpr.Match(ILCode.Newarr, out elementType, out lengthExpr) &&
lengthExpr.Match(ILCode.Ldc_I4, out arrayLength) && lengthExpr.Match(ILCode.Ldc_I4, out arrayLength) &&
arrayLength > 0) arrayLength > 0) {
{ ILExpression[] newArr;
MethodReference methodRef; int initArrayPos;
ILExpression methodArg1; if (ForwardScanInitializeArrayRuntimeHelper(body, pos + 1, v, elementType, arrayLength, out newArr, out initArrayPos)) {
ILExpression methodArg2; var arrayType = new ArrayType(elementType, 1);
FieldDefinition field; arrayType.Dimensions[0] = new ArrayDimension(0, arrayLength);
if (body.ElementAtOrDefault(pos + 1).Match(ILCode.Call, out methodRef, out methodArg1, out methodArg2) && body[pos] = new ILExpression(ILCode.Stloc, v, new ILExpression(ILCode.InitArray, arrayType, newArr));
methodRef.DeclaringType.FullName == "System.Runtime.CompilerServices.RuntimeHelpers" && body.RemoveAt(initArrayPos);
methodRef.Name == "InitializeArray" &&
methodArg1.Match(ILCode.Ldloc, out v2) &&
v == v2 &&
methodArg2.Match(ILCode.Ldtoken, out field) &&
field != null && field.InitialValue != null)
{
ILExpression[] newArr = new ILExpression[arrayLength];
if (DecodeArrayInitializer(TypeAnalysis.GetTypeCode(arrayType), field.InitialValue, newArr)) {
body[pos] = new ILExpression(ILCode.Stloc, v, new ILExpression(ILCode.InitArray, arrayType, newArr));
body.RemoveAt(pos + 1);
new ILInlining(method).InlineIfPossible(body, ref pos);
return true;
}
} }
// Put in a limit so that we don't consume too much memory if the code allocates a huge array // 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! // and populates it extremely sparsly. However, 255 "null" elements in a row actually occur in the Mono C# compiler!
const int maxConsecutiveDefaultValueExpressions = 300; const int maxConsecutiveDefaultValueExpressions = 300;
@ -77,10 +63,9 @@ namespace ICSharpCode.Decompiler.ILAst
v == v3 && v == v3 &&
nextExpr.Arguments[1].Match(ILCode.Ldc_I4, out arrayPos) && nextExpr.Arguments[1].Match(ILCode.Ldc_I4, out arrayPos) &&
arrayPos >= operands.Count && arrayPos >= operands.Count &&
arrayPos <= operands.Count + maxConsecutiveDefaultValueExpressions) arrayPos <= operands.Count + maxConsecutiveDefaultValueExpressions) {
{
while (operands.Count < arrayPos) while (operands.Count < arrayPos)
operands.Add(new ILExpression(ILCode.DefaultValue, arrayType)); operands.Add(new ILExpression(ILCode.DefaultValue, elementType));
operands.Add(nextExpr.Arguments[2]); operands.Add(nextExpr.Arguments[2]);
numberOfInstructionsToRemove++; numberOfInstructionsToRemove++;
} else { } else {
@ -88,16 +73,79 @@ namespace ICSharpCode.Decompiler.ILAst
} }
} }
if (operands.Count == arrayLength) { 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); expr.Arguments[0] = new ILExpression(ILCode.InitArray, arrayType, operands);
body.RemoveRange(pos + 1, numberOfInstructionsToRemove); body.RemoveRange(pos + 1, numberOfInstructionsToRemove);
new ILInlining(method).InlineIfPossible(body, ref pos); new ILInlining(method).InlineIfPossible(body, ref pos);
return true; return true;
} }
} }
return false; return false;
} }
bool TransformMultidimensionalArrayInitializers(List<ILNode> body, ILExpression expr, int pos)
{
ILVariable v, v2, v3;
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)) {
var mdArr = Array.CreateInstance(typeof(ILExpression), arrayLengths);
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)
{
for (; pos < body.Count; pos++) {
ILVariable v2;
MethodReference methodRef;
ILExpression methodArg1;
ILExpression methodArg2;
FieldDefinition field;
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 field) &&
field != null && field.InitialValue != null) {
ILExpression[] newArr = new ILExpression[arrayLength];
if (DecodeArrayInitializer(TypeAnalysis.GetTypeCode(arrayType.GetElementType()), field.InitialValue, newArr)) {
values = newArr;
foundPos = pos;
return true;
}
}
}
values = null;
foundPos = -1;
return false;
}
static bool DecodeArrayInitializer(TypeCode elementType, byte[] initialValue, ILExpression[] output) static bool DecodeArrayInitializer(TypeCode elementType, byte[] initialValue, ILExpression[] output)
{ {
switch (elementType) { switch (elementType) {
@ -160,7 +208,7 @@ namespace ICSharpCode.Decompiler.ILAst
} }
} }
#endregion #endregion
/// <summary> /// <summary>
/// Handles both object and collection initializers. /// Handles both object and collection initializers.
/// </summary> /// </summary>
@ -168,7 +216,7 @@ namespace ICSharpCode.Decompiler.ILAst
{ {
if (!context.Settings.ObjectOrCollectionInitializers) if (!context.Settings.ObjectOrCollectionInitializers)
return false; return false;
Debug.Assert(body[pos] == expr); // should be called for top-level expressions only Debug.Assert(body[pos] == expr); // should be called for top-level expressions only
ILVariable v; ILVariable v;
ILExpression newObjExpr; ILExpression newObjExpr;
@ -182,19 +230,19 @@ namespace ICSharpCode.Decompiler.ILAst
// don't use object initializer syntax for closures // don't use object initializer syntax for closures
if (Ast.Transforms.DelegateConstruction.IsPotentialClosure(context, ctor.DeclaringType.ResolveWithinSameModule())) if (Ast.Transforms.DelegateConstruction.IsPotentialClosure(context, ctor.DeclaringType.ResolveWithinSameModule()))
return false; return false;
ILExpression initializer = ParseObjectInitializer(body, ref pos, v, newObjExpr, IsCollectionType(ctor.DeclaringType)); ILExpression initializer = ParseObjectInitializer(body, ref pos, v, newObjExpr, IsCollectionType(ctor.DeclaringType));
if (initializer.Arguments.Count == 1) // only newobj argument, no initializer elements if (initializer.Arguments.Count == 1) // only newobj argument, no initializer elements
return false; return false;
int totalElementCount = pos - originalPos - 1; // totalElementCount: includes elements from nested collections int totalElementCount = pos - originalPos - 1; // totalElementCount: includes elements from nested collections
Debug.Assert(totalElementCount >= initializer.Arguments.Count - 1); Debug.Assert(totalElementCount >= initializer.Arguments.Count - 1);
// Verify that we can inline 'v' into the next instruction: // Verify that we can inline 'v' into the next instruction:
if (pos >= body.Count) if (pos >= body.Count)
return false; // reached end of block, but there should be another instruction which consumes the initialized object return false; // reached end of block, but there should be another instruction which consumes the initialized object
ILInlining inlining = new ILInlining(method); ILInlining inlining = new ILInlining(method);
// one ldloc for each initializer argument, and another ldloc for the use of the initialized object // one ldloc for each initializer argument, and another ldloc for the use of the initialized object
if (inlining.numLdloc.GetOrDefault(v) != totalElementCount + 1) if (inlining.numLdloc.GetOrDefault(v) != totalElementCount + 1)
@ -204,20 +252,20 @@ namespace ICSharpCode.Decompiler.ILAst
ILExpression nextExpr = body[pos] as ILExpression; ILExpression nextExpr = body[pos] as ILExpression;
if (!inlining.CanInlineInto(nextExpr, v, initializer)) if (!inlining.CanInlineInto(nextExpr, v, initializer))
return false; return false;
expr.Arguments[0] = initializer; expr.Arguments[0] = initializer;
// remove all the instructions that were pulled into the initializer // remove all the instructions that were pulled into the initializer
body.RemoveRange(originalPos + 1, pos - originalPos - 1); body.RemoveRange(originalPos + 1, pos - originalPos - 1);
// now that we know that it's an object initializer, change all the first arguments to 'InitializedObject' // now that we know that it's an object initializer, change all the first arguments to 'InitializedObject'
ChangeFirstArgumentToInitializedObject(initializer); ChangeFirstArgumentToInitializedObject(initializer);
inlining = new ILInlining(method); inlining = new ILInlining(method);
inlining.InlineIfPossible(body, ref originalPos); inlining.InlineIfPossible(body, ref originalPos);
return true; return true;
} }
/// <summary> /// <summary>
/// Gets whether the type supports collection initializers. /// Gets whether the type supports collection initializers.
/// </summary> /// </summary>
@ -233,7 +281,7 @@ namespace ICSharpCode.Decompiler.ILAst
} }
return false; return false;
} }
/// <summary> /// <summary>
/// Gets whether 'expr' represents a setter in an object initializer. /// Gets whether 'expr' represents a setter in an object initializer.
/// ('CallvirtSetter(Property, v, value)') /// ('CallvirtSetter(Property, v, value)')
@ -247,7 +295,7 @@ namespace ICSharpCode.Decompiler.ILAst
} }
return false; return false;
} }
/// <summary> /// <summary>
/// Gets whether 'expr' represents the invocation of an 'Add' method in a collection initializer. /// Gets whether 'expr' represents the invocation of an 'Add' method in a collection initializer.
/// </summary> /// </summary>
@ -262,7 +310,7 @@ namespace ICSharpCode.Decompiler.ILAst
} }
return false; return false;
} }
/// <summary> /// <summary>
/// Parses an object initializer. /// Parses an object initializer.
/// </summary> /// </summary>
@ -304,7 +352,7 @@ namespace ICSharpCode.Decompiler.ILAst
} }
return objectInitializer; return objectInitializer;
} }
static bool AdjustInitializerStack(List<ILExpression> initializerStack, ILExpression argument, ILVariable v, bool isCollection) static bool AdjustInitializerStack(List<ILExpression> initializerStack, ILExpression argument, ILVariable v, bool isCollection)
{ {
// Argument is of the form 'getter(getter(...(v)))' // Argument is of the form 'getter(getter(...(v)))'
@ -340,7 +388,7 @@ namespace ICSharpCode.Decompiler.ILAst
returnType = TypeAnalysis.GetFieldType((FieldReference)mr); returnType = TypeAnalysis.GetFieldType((FieldReference)mr);
else else
returnType = TypeAnalysis.SubstituteTypeArgs(((MethodReference)mr).ReturnType, mr); returnType = TypeAnalysis.SubstituteTypeArgs(((MethodReference)mr).ReturnType, mr);
ILExpression nestedInitializer = new ILExpression( ILExpression nestedInitializer = new ILExpression(
IsCollectionType(returnType) ? ILCode.InitCollection : ILCode.InitObject, IsCollectionType(returnType) ? ILCode.InitCollection : ILCode.InitObject,
null, g); null, g);
@ -375,7 +423,7 @@ namespace ICSharpCode.Decompiler.ILAst
} }
} }
} }
static void CleanupInitializerStackAfterFailedAdjustment(List<ILExpression> initializerStack) static void CleanupInitializerStackAfterFailedAdjustment(List<ILExpression> initializerStack)
{ {
// There might be empty nested initializers left over; so we'll remove those: // There might be empty nested initializers left over; so we'll remove those:
@ -386,7 +434,7 @@ namespace ICSharpCode.Decompiler.ILAst
initializerStack.RemoveAt(initializerStack.Count - 1); initializerStack.RemoveAt(initializerStack.Count - 1);
} }
} }
static void ChangeFirstArgumentToInitializedObject(ILExpression initializer) static void ChangeFirstArgumentToInitializedObject(ILExpression initializer)
{ {
// Go through all elements in the initializer (so skip the newobj-instr. at the start) // Go through all elements in the initializer (so skip the newobj-instr. at the start)

8
ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs

@ -568,11 +568,13 @@ namespace ICSharpCode.Decompiler.ILAst
InferTypeForExpression(expr.Arguments.Single(), typeSystem.Int32); InferTypeForExpression(expr.Arguments.Single(), typeSystem.Int32);
return new ArrayType((TypeReference)expr.Operand); return new ArrayType((TypeReference)expr.Operand);
case ILCode.InitArray: case ILCode.InitArray:
if (forceInferChildren) { var operandAsArrayType = (ArrayType)expr.Operand;
if (forceInferChildren)
{
foreach (ILExpression arg in expr.Arguments) foreach (ILExpression arg in expr.Arguments)
InferTypeForExpression(arg, (TypeReference)expr.Operand); InferTypeForExpression(arg, operandAsArrayType.ElementType);
} }
return new ArrayType((TypeReference)expr.Operand); return operandAsArrayType;
case ILCode.Ldlen: case ILCode.Ldlen:
return typeSystem.Int32; return typeSystem.Int32;
case ILCode.Ldelem_U1: case ILCode.Ldelem_U1:

253
ICSharpCode.Decompiler/Tests/InitializerTests.cs

@ -456,4 +456,257 @@ public class InitializerTests
} }
}); });
} }
public void MultidimensionalInit()
{
int[,] expr_09 = new int[, ]
{
{
0,
0,
0,
0
},
{
1,
1,
1,
1
},
{
0,
0,
0,
0
},
{
0,
0,
0,
0
},
{
0,
0,
1,
0
},
{
0,
0,
1,
0
},
{
0,
0,
1,
0
},
{
0,
0,
1,
0
},
{
0,
0,
0,
0
},
{
1,
1,
1,
1
},
{
0,
0,
0,
0
},
{
0,
0,
0,
0
},
{
0,
0,
1,
0
},
{
0,
0,
1,
0
},
{
0,
0,
1,
0
},
{
0,
0,
1,
0
}
};
}
public void MultidimensionalInit2()
{
int[][,] array = new int[][,]
{
new int[, ]
{
{
0,
0,
0,
0
},
{
1,
1,
1,
1
},
{
0,
0,
0,
0
},
{
0,
0,
0,
0
}
},
new int[, ]
{
{
0,
0,
1,
0
},
{
0,
0,
1,
0
},
{
0,
0,
1,
0
},
{
0,
0,
1,
0
}
},
new int[, ]
{
{
0,
0,
0,
0
},
{
1,
1,
1,
1
},
{
0,
0,
0,
0
},
{
0,
0,
0,
0
}
},
new int[, ]
{
{
0,
0,
1,
0
},
{
0,
0,
1,
0
},
{
0,
0,
1,
0
},
{
0,
0,
1,
0
}
}
};
}
} }

1
NRefactory/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs

@ -838,6 +838,7 @@ namespace ICSharpCode.NRefactory.CSharp
public object VisitEmptyExpression (EmptyExpression emptyExpression, object data) public object VisitEmptyExpression (EmptyExpression emptyExpression, object data)
{ {
StartNode(emptyExpression);
return EndNode (emptyExpression); return EndNode (emptyExpression);
} }
#region VisitPrimitiveExpression #region VisitPrimitiveExpression

Loading…
Cancel
Save