// Copyright (c) 2015 Siegfried Pammer // // 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.Linq; using System.Reflection.Metadata; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.IL.Transforms { /// /// Transforms array initialization pattern of System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray. /// For collection and object initializers see /// public class TransformArrayInitializers : IStatementTransform { StatementTransformContext context; void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) { if (!context.Settings.ArrayInitializers) return; this.context = context; try { if (DoTransform(context.Function, block, pos)) return; if (DoTransformMultiDim(context.Function, block, pos)) return; if (context.Settings.StackAllocInitializers && DoTransformStackAllocInitializer(block, pos)) return; if (DoTransformInlineRuntimeHelpersInitializeArray(block, pos)) return; } finally { this.context = null; } } bool DoTransform(ILFunction function, Block body, int pos) { if (pos >= body.Instructions.Count - 2) return false; ILInstruction inst = body.Instructions[pos]; if (inst.MatchStLoc(out var v, out var newarrExpr) && MatchNewArr(newarrExpr, out var elementType, out var arrayLength)) { if (HandleRuntimeHelpersInitializeArray(body, pos + 1, v, elementType, arrayLength, out var values, out var initArrayPos)) { context.Step("HandleRuntimeHelperInitializeArray: single-dim", inst); var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type); var block = BlockFromInitializer(tempStore, elementType, arrayLength, values); body.Instructions[pos] = new StLoc(v, block); body.Instructions.RemoveAt(initArrayPos); ILInlining.InlineIfPossible(body, pos, context); return true; } if (arrayLength.Length == 1) { if (HandleSimpleArrayInitializer(function, body, pos + 1, v, arrayLength, out var arrayValues, out var instructionsToRemove)) { context.Step("HandleSimpleArrayInitializer: single-dim", inst); var block = new Block(BlockKind.ArrayInitializer); var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type); block.Instructions.Add(new StLoc(tempStore, new NewArr(elementType, arrayLength.Select(l => new LdcI4(l)).ToArray()))); block.Instructions.AddRange(arrayValues.Select( t => { var (indices, value) = t; if (value == null) value = GetNullExpression(elementType); return StElem(new LdLoc(tempStore), indices, value, elementType); } )); block.FinalInstruction = new LdLoc(tempStore); body.Instructions[pos] = new StLoc(v, block); body.Instructions.RemoveRange(pos + 1, instructionsToRemove); ILInlining.InlineIfPossible(body, pos, context); return true; } if (HandleJaggedArrayInitializer(body, pos + 1, v, elementType, arrayLength[0], out ILVariable finalStore, out values, out instructionsToRemove)) { context.Step("HandleJaggedArrayInitializer: single-dim", inst); var block = new Block(BlockKind.ArrayInitializer); var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type); block.Instructions.Add(new StLoc(tempStore, new NewArr(elementType, arrayLength.Select(l => new LdcI4(l)).ToArray()))); block.Instructions.AddRange(values.SelectWithIndex((i, value) => StElem(new LdLoc(tempStore), new[] { new LdcI4(i) }, value, elementType))); block.FinalInstruction = new LdLoc(tempStore); body.Instructions[pos] = new StLoc(finalStore, block); body.Instructions.RemoveRange(pos + 1, instructionsToRemove); ILInlining.InlineIfPossible(body, pos, context); return true; } } } return false; } internal static bool TransformSpanTArrayInitialization(NewObj inst, StatementTransformContext context, out Block block) { block = null; if (!context.Settings.ArrayInitializers) return false; if (MatchSpanTCtorWithPointerAndSize(inst, context, out var elementType, out var field, out var size)) { if (field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA)) { var valuesList = new List(); var initialValue = field.GetInitialValue(context.PEFile.Reader, context.TypeSystem); if (DecodeArrayInitializer(elementType, initialValue, new[] { size }, valuesList)) { var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType)); block = BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray()); return true; } } } return false; } static bool MatchSpanTCtorWithPointerAndSize(NewObj newObj, StatementTransformContext context, out IType elementType, out FieldDefinition field, out int size) { field = default; size = default; elementType = null; IType type = newObj.Method.DeclaringType; if (!type.IsKnownType(KnownTypeCode.SpanOfT) && !type.IsKnownType(KnownTypeCode.ReadOnlySpanOfT)) return false; if (newObj.Arguments.Count != 2 || type.TypeArguments.Count != 1) return false; elementType = type.TypeArguments[0]; if (!newObj.Arguments[0].UnwrapConv(ConversionKind.StopGCTracking).MatchLdsFlda(out var member)) return false; if (member.MetadataToken.IsNil) return false; if (!newObj.Arguments[1].MatchLdcI4(out size)) return false; field = context.PEFile.Metadata.GetFieldDefinition((FieldDefinitionHandle)member.MetadataToken); return true; } bool DoTransformMultiDim(ILFunction function, Block body, int pos) { if (pos >= body.Instructions.Count - 2) return false; ILInstruction inst = body.Instructions[pos]; if (inst.MatchStLoc(out var v, out var newarrExpr) && MatchNewArr(newarrExpr, out var elementType, out var length)) { if (HandleRuntimeHelpersInitializeArray(body, pos + 1, v, elementType, length, out var values, out var initArrayPos)) { context.Step("HandleRuntimeHelpersInitializeArray: multi-dim", inst); var block = BlockFromInitializer(v, elementType, length, values); body.Instructions[pos].ReplaceWith(new StLoc(v, block)); body.Instructions.RemoveAt(initArrayPos); ILInlining.InlineIfPossible(body, pos, context); return true; } if (HandleSimpleArrayInitializer(function, body, pos + 1, v, length, out var arrayValues, out var instructionsToRemove)) { context.Step("HandleSimpleArrayInitializer: multi-dim", inst); var block = new Block(BlockKind.ArrayInitializer); var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type); block.Instructions.Add(new StLoc(tempStore, new NewArr(elementType, length.Select(l => new LdcI4(l)).ToArray()))); block.Instructions.AddRange(arrayValues.Select( t => { var (indices, value) = t; if (value == null) value = GetNullExpression(elementType); return StElem(new LdLoc(tempStore), indices, value, elementType); } )); block.FinalInstruction = new LdLoc(tempStore); body.Instructions[pos] = new StLoc(v, block); body.Instructions.RemoveRange(pos + 1, instructionsToRemove); ILInlining.InlineIfPossible(body, pos, context); return true; } } return false; } bool DoTransformStackAllocInitializer(Block body, int pos) { if (pos >= body.Instructions.Count - 2) return false; ILInstruction inst = body.Instructions[pos]; if (inst.MatchStLoc(out var v, out var locallocExpr) && locallocExpr.MatchLocAlloc(out var lengthInst)) { if (lengthInst.MatchLdcI(out var lengthInBytes) && HandleCpblkInitializer(body, pos + 1, v, lengthInBytes, out var blob, out var elementType)) { context.Step("HandleCpblkInitializer", inst); var block = new Block(BlockKind.StackAllocInitializer); var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new PointerType(elementType)); block.Instructions.Add(new StLoc(tempStore, locallocExpr)); while (blob.RemainingBytes > 0) { block.Instructions.Add(StElemPtr(tempStore, blob.Offset, new LdcI4(blob.ReadByte()), elementType)); } block.FinalInstruction = new LdLoc(tempStore); body.Instructions[pos] = new StLoc(v, block); body.Instructions.RemoveAt(pos + 1); ILInlining.InlineIfPossible(body, pos, context); ExpressionTransforms.RunOnSingleStatement(body.Instructions[pos], context); return true; } if (HandleSequentialLocAllocInitializer(body, pos + 1, v, locallocExpr, out elementType, out StObj[] values, out int instructionsToRemove)) { context.Step("HandleSequentialLocAllocInitializer", inst); var block = new Block(BlockKind.StackAllocInitializer); var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new PointerType(elementType)); block.Instructions.Add(new StLoc(tempStore, locallocExpr)); block.Instructions.AddRange(values.Where(value => value != null).Select(value => RewrapStore(tempStore, value, elementType))); block.FinalInstruction = new LdLoc(tempStore); body.Instructions[pos] = new StLoc(v, block); body.Instructions.RemoveRange(pos + 1, instructionsToRemove); ILInlining.InlineIfPossible(body, pos, context); ExpressionTransforms.RunOnSingleStatement(body.Instructions[pos], context); return true; } } return false; } bool HandleCpblkInitializer(Block block, int pos, ILVariable v, long length, out BlobReader blob, out IType elementType) { blob = default; elementType = null; if (!block.Instructions[pos].MatchCpblk(out var dest, out var src, out var size)) return false; if (!dest.MatchLdLoc(v) || !src.MatchLdsFlda(out var field) || !size.MatchLdcI4((int)length)) return false; if (!(v.IsSingleDefinition && v.LoadCount == 2)) return false; if (field.MetadataToken.IsNil) return false; if (!block.Instructions[pos + 1].MatchStLoc(out var finalStore, out var value)) { var otherLoadOfV = v.LoadInstructions.FirstOrDefault(l => !(l.Parent is Cpblk)); if (otherLoadOfV == null) return false; finalStore = otherLoadOfV.Parent.Extract(context); if (finalStore == null) return false; value = ((StLoc)finalStore.StoreInstructions[0]).Value; } var fd = context.PEFile.Metadata.GetFieldDefinition((FieldDefinitionHandle)field.MetadataToken); if (!fd.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA)) return false; if (value.MatchLdLoc(v)) { elementType = ((PointerType)finalStore.Type).ElementType; } else if (value is NewObj { Arguments: { Count: 2 } } newObj && newObj.Method.DeclaringType.IsKnownType(KnownTypeCode.SpanOfT) && newObj.Arguments[0].MatchLdLoc(v) && newObj.Arguments[1].MatchLdcI4((int)length)) { elementType = ((ParameterizedType)newObj.Method.DeclaringType).TypeArguments[0]; } else { return false; } blob = fd.GetInitialValue(context.PEFile.Reader, context.TypeSystem); return true; } bool HandleSequentialLocAllocInitializer(Block block, int pos, ILVariable store, ILInstruction locAllocInstruction, out IType elementType, out StObj[] values, out int instructionsToRemove) { int elementCount = 0; long minExpectedOffset = 0; values = null; elementType = null; instructionsToRemove = 0; if (!locAllocInstruction.MatchLocAlloc(out var lengthInstruction)) return false; if (block.Instructions[pos].MatchInitblk(out var dest, out var value, out var size) && lengthInstruction.MatchLdcI(out long byteCount)) { if (!dest.MatchLdLoc(store) || !size.MatchLdcI(byteCount)) return false; instructionsToRemove++; pos++; } for (int i = pos; i < block.Instructions.Count; i++) { // match the basic stobj pattern if (!block.Instructions[i].MatchStObj(out ILInstruction target, out value, out var currentType) || value.Descendants.OfType().Any(inst => inst.Variable == store)) break; if (elementType != null && !currentType.Equals(elementType)) break; elementType = currentType; // match the target // should be either ldloc store (at offset 0) // or binary.add(ldloc store, offset) where offset is either 'elementSize' or 'i * elementSize' if (!target.MatchLdLoc(store)) { if (!target.MatchBinaryNumericInstruction(BinaryNumericOperator.Add, out var left, out var right)) return false; if (!left.MatchLdLoc(store)) break; var offsetInst = PointerArithmeticOffset.Detect(right, elementType, ((BinaryNumericInstruction)target).CheckForOverflow); if (offsetInst == null) return false; if (!offsetInst.MatchLdcI(out long offset) || offset < 0 || offset < minExpectedOffset) break; minExpectedOffset = offset; } if (values == null) { var countInstruction = PointerArithmeticOffset.Detect(lengthInstruction, elementType, checkForOverflow: true); if (countInstruction == null || !countInstruction.MatchLdcI(out long valuesLength) || valuesLength < 1) return false; values = new StObj[(int)valuesLength]; } if (minExpectedOffset >= values.Length) break; values[minExpectedOffset] = (StObj)block.Instructions[i]; elementCount++; } if (values == null || store.Kind != VariableKind.StackSlot || store.StoreCount != 1 || store.AddressCount != 0 || store.LoadCount > values.Length + 1) return false; if (store.LoadInstructions.Last().Parent is StLoc finalStore) { elementType = ((PointerType)finalStore.Variable.Type).ElementType; } instructionsToRemove += elementCount; return elementCount <= values.Length; } ILInstruction RewrapStore(ILVariable target, StObj storeInstruction, IType type) { ILInstruction targetInst; if (storeInstruction.Target.MatchLdLoc(out _)) targetInst = new LdLoc(target); else if (storeInstruction.Target.MatchBinaryNumericInstruction(BinaryNumericOperator.Add, out var left, out var right)) { var old = (BinaryNumericInstruction)storeInstruction.Target; targetInst = new BinaryNumericInstruction(BinaryNumericOperator.Add, new LdLoc(target), right, old.CheckForOverflow, old.Sign); } else throw new NotSupportedException("This should never happen: Bug in HandleSequentialLocAllocInitializer!"); return new StObj(targetInst, storeInstruction.Value, storeInstruction.Type); } ILInstruction StElemPtr(ILVariable target, int offset, LdcI4 value, IType type) { var targetInst = offset == 0 ? (ILInstruction)new LdLoc(target) : new BinaryNumericInstruction( BinaryNumericOperator.Add, new LdLoc(target), new Conv(new LdcI4(offset), PrimitiveType.I, false, Sign.Signed), false, Sign.None ); return new StObj(targetInst, value, type); } /// /// Handle simple case where RuntimeHelpers.InitializeArray is not used. /// internal static bool HandleSimpleArrayInitializer(ILFunction function, Block block, int pos, ILVariable store, int[] arrayLength, out (ILInstruction[] Indices, ILInstruction Value)[] values, out int instructionsToRemove) { instructionsToRemove = 0; int elementCount = 0; int length = arrayLength.Aggregate(1, (t, l) => t * l); // Cannot pre-allocate the result array, because we do not know yet, // whether there is in fact an array initializer. // To prevent excessive allocations, use min(|block|, arraySize) als initial capacity. // This should prevent list-resizing as well as out-of-memory errors. values = null; var valuesList = new List<(ILInstruction[] Indices, ILInstruction Value)>(Math.Min(block.Instructions.Count, length)); int[] nextMinimumIndex = new int[arrayLength.Length]; ILInstruction[] CalculateNextIndices(InstructionCollection indices, out bool exactMatch) { var nextIndices = new ILInstruction[arrayLength.Length]; exactMatch = true; if (indices == null) { for (int k = 0; k < nextIndices.Length; k++) { nextIndices[k] = new LdcI4(nextMinimumIndex[k]); } } else { bool previousComponentWasGreater = false; for (int k = 0; k < indices.Count; k++) { if (!indices[k].MatchLdcI4(out int index)) return null; // index must be in range [0..length[ and must be greater than or equal to nextMinimumIndex // to avoid running out of bounds or accidentally reordering instructions or overwriting previous instructions. // However, leaving array slots empty is allowed, as those are filled with default values when the // initializer block is generated. if (index < 0 || index >= arrayLength[k] || (!previousComponentWasGreater && index < nextMinimumIndex[k])) return null; nextIndices[k] = new LdcI4(nextMinimumIndex[k]); if (index != nextMinimumIndex[k]) { exactMatch = false; // this flag allows us to check whether the whole set of indices is smaller: // [3, 3] should be smaller than [4, 0] even though 3 > 0. if (index > nextMinimumIndex[k]) previousComponentWasGreater = true; } } } for (int k = nextMinimumIndex.Length - 1; k >= 0; k--) { nextMinimumIndex[k]++; if (nextMinimumIndex[k] < arrayLength[k] || k == 0) break; nextMinimumIndex[k] = 0; } return nextIndices; } int i = pos; int step; while (i < block.Instructions.Count) { InstructionCollection indices; // stobj elementType(ldelema elementType(ldloc store, indices), value) if (block.Instructions[i].MatchStObj(out ILInstruction target, out ILInstruction value, out IType type)) { if (!(target is LdElema ldelem && ldelem.Array.MatchLdLoc(store))) break; indices = ldelem.Indices; step = 1; // stloc s(ldelema elementType(ldloc store, indices)) // stobj elementType(ldloc s, value) } else if (block.Instructions[i].MatchStLoc(out var addressTemporary, out var addressValue)) { if (!(addressTemporary.IsSingleDefinition && addressTemporary.LoadCount == 1)) break; if (!(addressValue is LdElema ldelem && ldelem.Array.MatchLdLoc(store))) break; if (!(i + 1 < block.Instructions.Count)) break; if (!block.Instructions[i + 1].MatchStObj(out target, out value, out type)) break; if (!(target.MatchLdLoc(addressTemporary) && ldelem.Array.MatchLdLoc(store))) break; indices = ldelem.Indices; step = 2; } else { break; } if (value.Descendants.OfType().Any(inst => inst.Variable == store)) break; if (indices.Count != arrayLength.Length) break; bool exact; if (length <= 0) break; do { var nextIndices = CalculateNextIndices(indices, out exact); if (nextIndices == null) return false; if (exact) { valuesList.Add((nextIndices, value)); elementCount++; instructionsToRemove += step; } else { valuesList.Add((nextIndices, null)); } } while (valuesList.Count < length && !exact); i += step; } if (i < block.Instructions.Count) { if (block.Instructions[i].MatchStObj(out ILInstruction target, out ILInstruction value, out IType type)) { // An element of the array is modified directly after the initializer: // Abort transform, so that partial initializers are not constructed. if (target is LdElema ldelem && ldelem.Array.MatchLdLoc(store)) return false; } } if (pos + instructionsToRemove >= block.Instructions.Count) return false; if (elementCount == 0) return false; bool mustTransform = ILInlining.IsCatchWhenBlock(block) || ILInlining.IsInConstructorInitializer(function, block.Instructions[pos]); // If there are not enough user-defined elements after scanning all instructions, we can abort the transform. // This avoids unnecessary allocations and OOM crashes (in case of int.MaxValue). if (elementCount < length / 3 - 5) { if (!mustTransform) return false; // .NET does not allow allocation of arrays >= 2 GB. if (length >= int.MaxValue) return false; } for (int j = valuesList.Count; j < length; j++) { var nextIndices = CalculateNextIndices(null, out _); if (nextIndices == null) return false; valuesList.Add((nextIndices, null)); } values = valuesList.ToArray(); return true; } bool HandleJaggedArrayInitializer(Block block, int pos, ILVariable store, IType elementType, int length, out ILVariable finalStore, out ILInstruction[] values, out int instructionsToRemove) { instructionsToRemove = 0; finalStore = null; // Cannot pre-allocate the result array, because we do not know yet, // whether there is in fact an array initializer. // To prevent excessive allocations, use min(|block|, arraySize) als initial capacity. // This should prevent list-resizing as well as out-of-memory errors. values = null; var valuesList = new List(Math.Min(block.Instructions.Count, length)); for (int i = 0; i < length; i++) { // 1. Instruction: (optional) temporary copy of store bool hasTemporaryCopy = block.Instructions[pos].MatchStLoc(out var temp, out var storeLoad) && storeLoad.MatchLdLoc(store); ILInstruction initializer; if (hasTemporaryCopy) { if (!MatchJaggedArrayStore(block, pos + 1, temp, i, out initializer, out _)) return false; } else { if (!MatchJaggedArrayStore(block, pos, store, i, out initializer, out _)) return false; } valuesList.Add(initializer); int inc = hasTemporaryCopy ? 3 : 2; pos += inc; instructionsToRemove += inc; } // In case there is an extra copy of the store variable // Remove it and use its value instead. if (block.Instructions[pos].MatchStLoc(out finalStore, out var array)) { if (!array.MatchLdLoc(store)) return false; instructionsToRemove++; } else { finalStore = store; } values = valuesList.ToArray(); return true; } bool MatchJaggedArrayStore(Block block, int pos, ILVariable store, int index, out ILInstruction initializer, out IType type) { initializer = null; type = null; // 3. Instruction: stobj(ldelema(ldloc temp, ldc.i4 0), ldloc tempArrayLoad) var finalInstruction = block.Instructions.ElementAtOrDefault(pos + 1); if (finalInstruction == null || !finalInstruction.MatchStObj(out var tempAccess, out var tempArrayLoad, out type) || !tempArrayLoad.MatchLdLoc(out var initializerStore)) { return false; } if (!(tempAccess is LdElema elemLoad) || !elemLoad.Array.MatchLdLoc(store) || elemLoad.Indices.Count != 1 || !elemLoad.Indices[0].MatchLdcI4(index)) { return false; } // 2. Instruction: stloc(temp) with block (array initializer) var nextInstruction = block.Instructions.ElementAtOrDefault(pos); return nextInstruction != null && nextInstruction.MatchStLoc(initializerStore, out initializer) && initializer.OpCode == OpCode.Block; } static Block BlockFromInitializer(ILVariable v, IType elementType, int[] arrayLength, ILInstruction[] values) { var block = new Block(BlockKind.ArrayInitializer); block.Instructions.Add(new StLoc(v, new NewArr(elementType, arrayLength.Select(l => new LdcI4(l)).ToArray()))); int step = arrayLength.Length + 1; var indices = new List(); for (int i = 0; i < values.Length / step; i++) { // values array is filled backwards var value = values[step * i]; indices.EnsureCapacity(step - 1); for (int j = step - 1; j >= 1; j--) { indices.Add(values[step * i + j]); } block.Instructions.Add(StElem(new LdLoc(v), indices.ToArray(), value, elementType)); indices.Clear(); } block.FinalInstruction = new LdLoc(v); return block; } static bool MatchNewArr(ILInstruction instruction, out IType arrayType, out int[] length) { length = null; arrayType = null; if (!(instruction is NewArr newArr)) return false; arrayType = newArr.Type; var args = newArr.Indices; length = new int[args.Count]; for (int i = 0; i < args.Count; i++) { if (!args[i].MatchLdcI4(out int value) || value <= 0) return false; length[i] = value; } return true; } bool MatchInitializeArrayCall(ILInstruction instruction, out ILInstruction array, out FieldDefinition field) { array = null; field = default; if (!(instruction is Call call) || call.Arguments.Count != 2) return false; IMethod method = call.Method; if (!method.IsStatic || method.Name != "InitializeArray" || method.DeclaringTypeDefinition == null) return false; var declaringType = method.DeclaringTypeDefinition; if (declaringType.DeclaringType != null || declaringType.Name != "RuntimeHelpers" || declaringType.Namespace != "System.Runtime.CompilerServices") { return false; } array = call.Arguments[0]; if (!call.Arguments[1].MatchLdMemberToken(out var member)) return false; if (member.MetadataToken.IsNil) return false; field = context.PEFile.Metadata.GetFieldDefinition((FieldDefinitionHandle)member.MetadataToken); return true; } bool HandleRuntimeHelpersInitializeArray(Block body, int pos, ILVariable array, IType arrayType, int[] arrayLength, out ILInstruction[] values, out int foundPos) { if (MatchInitializeArrayCall(body.Instructions[pos], out var arrayInst, out var field) && arrayInst.MatchLdLoc(array)) { if (field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA)) { var valuesList = new List(); var initialValue = field.GetInitialValue(context.PEFile.Reader, context.TypeSystem); if (DecodeArrayInitializer(arrayType, initialValue, arrayLength, valuesList)) { values = valuesList.ToArray(); foundPos = pos; return true; } } } values = null; foundPos = -1; return false; } /// /// call InitializeArray(newarr T(size), ldmembertoken fieldToken) /// => /// Block (ArrayInitializer) { /// stloc i(newarr T(size)) /// stobj T(ldelema T(... indices ...), value) /// final: ldloc i /// } /// bool DoTransformInlineRuntimeHelpersInitializeArray(Block body, int pos) { var inst = body.Instructions[pos]; if (!MatchInitializeArrayCall(inst, out var arrayInst, out var field)) return false; if (!MatchNewArr(arrayInst, out var elementType, out var arrayLength)) return false; if (!field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA)) return false; var valuesList = new List(); var initialValue = field.GetInitialValue(context.PEFile.Reader, context.TypeSystem); if (!DecodeArrayInitializer(elementType, initialValue, arrayLength, valuesList)) return false; context.Step("InlineRuntimeHelpersInitializeArray: single-dim", inst); var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType, arrayLength.Length)); var block = BlockFromInitializer(tempStore, elementType, arrayLength, valuesList.ToArray()); body.Instructions[pos] = block; ILInlining.InlineIfPossible(body, pos, context); return true; } static bool DecodeArrayInitializer(IType type, BlobReader initialValue, int[] arrayLength, List output) { TypeCode typeCode = ReflectionHelper.GetTypeCode(type); switch (typeCode) { case TypeCode.Boolean: case TypeCode.Byte: return DecodeArrayInitializer(initialValue, arrayLength, output, typeCode, (ref BlobReader r) => new LdcI4(r.ReadByte())); case TypeCode.SByte: return DecodeArrayInitializer(initialValue, arrayLength, output, typeCode, (ref BlobReader r) => new LdcI4(r.ReadSByte())); case TypeCode.Int16: return DecodeArrayInitializer(initialValue, arrayLength, output, typeCode, (ref BlobReader r) => new LdcI4(r.ReadInt16())); case TypeCode.Char: case TypeCode.UInt16: return DecodeArrayInitializer(initialValue, arrayLength, output, typeCode, (ref BlobReader r) => new LdcI4(r.ReadUInt16())); case TypeCode.Int32: case TypeCode.UInt32: return DecodeArrayInitializer(initialValue, arrayLength, output, typeCode, (ref BlobReader r) => new LdcI4(r.ReadInt32())); case TypeCode.Int64: case TypeCode.UInt64: return DecodeArrayInitializer(initialValue, arrayLength, output, typeCode, (ref BlobReader r) => new LdcI8(r.ReadInt64())); case TypeCode.Single: return DecodeArrayInitializer(initialValue, arrayLength, output, typeCode, (ref BlobReader r) => new LdcF4(r.ReadSingle())); case TypeCode.Double: return DecodeArrayInitializer(initialValue, arrayLength, output, typeCode, (ref BlobReader r) => new LdcF8(r.ReadDouble())); case TypeCode.Object: case TypeCode.Empty: var typeDef = type.GetDefinition(); if (typeDef != null && typeDef.Kind == TypeKind.Enum) return DecodeArrayInitializer(typeDef.EnumUnderlyingType, initialValue, arrayLength, output); return false; default: return false; } } delegate ILInstruction ValueDecoder(ref BlobReader reader); static bool DecodeArrayInitializer(BlobReader initialValue, int[] arrayLength, List output, TypeCode elementType, ValueDecoder decoder) { int elementSize = ElementSizeOf(elementType); var totalLength = arrayLength.Aggregate(1, (t, l) => t * l); if (initialValue.RemainingBytes < (totalLength * elementSize)) return false; output.EnsureCapacity(totalLength + totalLength * arrayLength.Length); for (int i = 0; i < totalLength; i++) { output.Add(decoder(ref initialValue)); int next = i; for (int j = arrayLength.Length - 1; j >= 0; j--) { output.Add(new LdcI4(next % arrayLength[j])); next /= arrayLength[j]; } } return true; } static ILInstruction StElem(ILInstruction array, ILInstruction[] indices, ILInstruction value, IType type) { if (type.GetStackType() != value.ResultType) { value = new Conv(value, type.ToPrimitiveType(), false, Sign.None); } return new StObj(new LdElema(type, array, indices) { DelayExceptions = true }, value, type); } internal static ILInstruction GetNullExpression(IType elementType) { ITypeDefinition typeDef = elementType.GetEnumUnderlyingType().GetDefinition(); if (typeDef == null) return new DefaultValue(elementType); switch (typeDef.KnownTypeCode) { case KnownTypeCode.Boolean: case KnownTypeCode.Char: case KnownTypeCode.SByte: case KnownTypeCode.Byte: case KnownTypeCode.Int16: case KnownTypeCode.UInt16: case KnownTypeCode.Int32: case KnownTypeCode.UInt32: return new LdcI4(0); case KnownTypeCode.Int64: case KnownTypeCode.UInt64: return new LdcI8(0); case KnownTypeCode.Single: return new LdcF4(0); case KnownTypeCode.Double: return new LdcF8(0); case KnownTypeCode.Decimal: return new LdcDecimal(0); case KnownTypeCode.Void: throw new ArgumentException("void is not a valid element type!"); case KnownTypeCode.IntPtr: case KnownTypeCode.UIntPtr: default: return new DefaultValue(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(nameof(elementType)); } } } }