diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index bf315eee2..130c24bac 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -286,20 +286,26 @@ namespace ICSharpCode.Decompiler.CSharp return expr.WithILInstruction(inst) .WithRR(new ArrayCreateResolveResult(new ArrayType(compilation, inst.Type, dimensions), args.Select(a => a.ResolveResult).ToList(), new ResolveResult[0])); } - + protected internal override TranslatedExpression VisitLocAlloc(LocAlloc inst, TranslationContext context) + { + return TranslateLocAlloc(inst, context.TypeHint, out var elementType) + .WithILInstruction(inst).WithRR(new ResolveResult(new PointerType(elementType))); + } + + StackAllocExpression TranslateLocAlloc(LocAlloc inst, IType typeHint, out IType elementType) { TranslatedExpression countExpression; PointerType pointerType; if (inst.Argument.MatchBinaryNumericInstruction(BinaryNumericOperator.Mul, out var left, out var right) - && right.UnwrapConv(ConversionKind.SignExtend).UnwrapConv(ConversionKind.ZeroExtend).MatchSizeOf(out var elementType)) + && right.UnwrapConv(ConversionKind.SignExtend).UnwrapConv(ConversionKind.ZeroExtend).MatchSizeOf(out elementType)) { // Determine the element type from the sizeof countExpression = Translate(left.UnwrapConv(ConversionKind.ZeroExtend)); pointerType = new PointerType(elementType); } else { // Determine the element type from the expected pointer type in this context - pointerType = context.TypeHint as PointerType; + pointerType = typeHint as PointerType; if (pointerType != null && GetPointerArithmeticOffset( inst.Argument, Translate(inst.Argument), pointerType, checkForOverflow: true, @@ -318,7 +324,7 @@ namespace ICSharpCode.Decompiler.CSharp return new StackAllocExpression { Type = ConvertType(elementType), CountExpression = countExpression - }.WithILInstruction(inst).WithRR(new ResolveResult(new PointerType(elementType))); + }; } protected internal override TranslatedExpression VisitLdcI4(LdcI4 inst, TranslationContext context) @@ -1972,6 +1978,8 @@ namespace ICSharpCode.Decompiler.CSharp switch (block.Kind) { case BlockKind.ArrayInitializer: return TranslateArrayInitializer(block); + case BlockKind.StackAllocInitializer: + return TranslateStackAllocInitializer(block, context.TypeHint); case BlockKind.CollectionInitializer: case BlockKind.ObjectInitializer: return TranslateObjectAndCollectionInitializer(block); @@ -2123,9 +2131,9 @@ namespace ICSharpCode.Decompiler.CSharp if (stloc == null || final == null || !stloc.Value.MatchNewArr(out type) || stloc.Variable != final.Variable || stloc.Variable.Kind != VariableKind.InitializerTarget) throw new ArgumentException("given Block is invalid!"); var newArr = (NewArr)stloc.Value; - + var translatedDimensions = newArr.Indices.Select(i => Translate(i)).ToArray(); - + if (!translatedDimensions.All(dim => dim.ResolveResult.IsCompileTimeConstant)) throw new ArgumentException("given Block is invalid!"); int dimensions = newArr.Indices.Count; @@ -2134,7 +2142,7 @@ namespace ICSharpCode.Decompiler.CSharp var root = new ArrayInitializerExpression(); container.Push(root); var elementResolveResults = new List(); - + for (int i = 1; i < block.Instructions.Count; i++) { ILInstruction target, value, array; IType t; @@ -2181,7 +2189,44 @@ namespace ICSharpCode.Decompiler.CSharp return expr.WithILInstruction(block) .WithRR(new ArrayCreateResolveResult(new ArrayType(compilation, type, dimensions), newArr.Indices.Select(i => Translate(i).ResolveResult).ToArray(), elementResolveResults)); } - + + TranslatedExpression TranslateStackAllocInitializer(Block block, IType typeHint) + { + var stloc = block.Instructions.FirstOrDefault() as StLoc; + var final = block.FinalInstruction as LdLoc; + if (stloc == null || final == null || stloc.Variable != final.Variable || stloc.Variable.Kind != VariableKind.InitializerTarget) + throw new ArgumentException("given Block is invalid!"); + if (!(stloc.Value is LocAlloc locAlloc)) + throw new ArgumentException("given Block is invalid!"); + + var stackAllocExpression = TranslateLocAlloc(locAlloc, typeHint, out var elementType); + var initializer = stackAllocExpression.Initializer = new ArrayInitializerExpression(); + var pointerType = new PointerType(elementType); + + for (int i = 1; i < block.Instructions.Count; i++) { + // stobj type(binary.add.i(ldloc I_0, conv i4->i (ldc.i4 offset)), value) + if (!block.Instructions[i].MatchStObj(out var target, out var value, out var t) || !TypeUtils.IsCompatibleTypeForMemoryAccess(elementType, t)) + throw new ArgumentException("given Block is invalid!"); + if (i == 1) { + if (!target.MatchLdLoc(stloc.Variable)) + throw new ArgumentException("given Block is invalid!"); + } else { + if (!target.MatchBinaryNumericInstruction(BinaryNumericOperator.Add, out var left, out var right)) + throw new ArgumentException("given Block is invalid!"); + var binary = (BinaryNumericInstruction)target; + var offsetInst = PointerArithmeticOffset.Detect(right, pointerType, binary.CheckForOverflow); + if (!left.MatchLdLoc(final.Variable) || offsetInst == null) + throw new ArgumentException("given Block is invalid!"); + if (!offsetInst.MatchLdcI(i - 1)) + throw new ArgumentException("given Block is invalid!"); + } + var val = Translate(value, typeHint: elementType).ConvertTo(elementType, this, allowImplicitConversion: true); + initializer.Elements.Add(val); + } + return stackAllocExpression.WithILInstruction(block) + .WithRR(new ResolveResult(stloc.Variable.Type)); + } + TranslatedExpression TranslatePostfixOperator(Block block) { var targetInst = (block.Instructions.ElementAtOrDefault(0) as StLoc)?.Value; diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 39c24387f..2bf898f63 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -89,13 +89,14 @@ namespace ICSharpCode.Decompiler } if (languageVersion < CSharp.LanguageVersion.CSharp7_3) { //introduceUnmanagedTypeConstraint = false; + stackAllocInitializers = false; tupleComparisons = false; } } public CSharp.LanguageVersion GetMinimumRequiredVersion() { - if (tupleComparisons) + if (tupleComparisons || stackAllocInitializers) return CSharp.LanguageVersion.CSharp7_3; if (introduceRefModifiersOnStructs || introduceReadonlyAndInModifiers || nonTrailingNamedArguments) return CSharp.LanguageVersion.CSharp7_2; @@ -682,6 +683,21 @@ namespace ICSharpCode.Decompiler } } + bool stackAllocInitializers = true; + + /// + /// Gets/Sets whether C# 7.3 stackalloc initializers should be used. + /// + public bool StackAllocInitializers { + get { return stackAllocInitializers; } + set { + if (stackAllocInitializers != value) { + stackAllocInitializers = value; + OnPropertyChanged(); + } + } + } + bool tupleTypes = true; /// diff --git a/ICSharpCode.Decompiler/IL/Instructions/Block.cs b/ICSharpCode.Decompiler/IL/Instructions/Block.cs index f26cbfb27..4d6cd9784 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Block.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Block.cs @@ -308,6 +308,7 @@ namespace ICSharpCode.Decompiler.IL ArrayInitializer, CollectionInitializer, ObjectInitializer, + StackAllocInitializer, /// /// Block is used for postfix operator on local variable. /// diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs index b9e22e735..3966f8617 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs @@ -39,8 +39,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms return; this.context = context; try { - if (!DoTransform(block, pos)) - DoTransformMultiDim(block, pos); + if (DoTransform(block, pos)) + return; + if (DoTransformMultiDim(block, pos)) + return; + if (context.Settings.StackAllocInitializers && DoTransformStackAllocInitializer(block, pos)) + return; } finally { this.context = null; } @@ -152,16 +156,159 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; } + bool DoTransformStackAllocInitializer(Block body, int pos) + { + if (pos >= body.Instructions.Count - 2) + return false; + ILInstruction inst = body.Instructions[pos]; + IType elementType; + 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 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, new LocAlloc(lengthInst))); + + 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); + return true; + } + if (HandleSequentialLocAllocInitializer(body, pos + 1, v, lengthInst, out elementType, out StObj[] values)) { + 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, new LocAlloc(lengthInst))); + block.Instructions.AddRange(values.Select(value => RewrapStore(tempStore, value, elementType))); + block.FinalInstruction = new LdLoc(tempStore); + body.Instructions[pos] = new StLoc(v, block); + body.Instructions.RemoveRange(pos + 1, values.Length); + ILInlining.InlineIfPossible(body, 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 (field.MetadataToken.IsNil) + return false; + if (!block.Instructions[pos + 1].MatchStLoc(out var finalStore, out var value)) + return false; + if (!value.MatchLdLoc(v)) + return false; + var fd = context.PEFile.Metadata.GetFieldDefinition((FieldDefinitionHandle)field.MetadataToken); + if (!fd.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA)) + return false; + blob = fd.GetInitialValue(context.PEFile.Reader, context.TypeSystem); + elementType = ((PointerType)finalStore.Type).ElementType; + return true; + } + + bool HandleSequentialLocAllocInitializer(Block block, int pos, ILVariable store, ILInstruction lengthInstruction, out IType elementType, out StObj[] values) + { + int elementCount = 0; + values = null; + elementType = null; + + for (int i = pos; i < block.Instructions.Count; i++) { + // match the basic stobj pattern + if (!block.Instructions[i].MatchStObj(out ILInstruction target, out ILInstruction 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 (elementCount == 0) { + if (!target.MatchLdLoc(store)) + break; + } else { + if (!target.MatchBinaryNumericInstruction(BinaryNumericOperator.Add, out var left, out var right)) + break; + if (!left.MatchLdLoc(store)) + break; + var offsetInst = PointerArithmeticOffset.Detect(right, new PointerType(elementType), ((BinaryNumericInstruction)target).CheckForOverflow); + if (offsetInst == null) + break; + if (!offsetInst.MatchLdcI(elementCount)) + break; + } + if (values == null) { + var countInstruction = PointerArithmeticOffset.Detect(lengthInstruction, new PointerType(elementType), checkForOverflow: true); + if (countInstruction == null || !countInstruction.MatchLdcI(out long valuesLength) || valuesLength < 1) + return false; + values = new StObj[(int)valuesLength]; + } + if (i - pos >= values.Length) + break; + values[i - pos] = (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; + + var finalStore = store.LoadInstructions.Last().Parent as StLoc; + + if (finalStore == null) + return false; + + elementType = ((PointerType)finalStore.Variable.Type).ElementType; + + 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(Block block, int pos, ILVariable store, IType elementType, int length, out ILInstruction[] values, out int instructionsToRemove) + internal static bool HandleSimpleArrayInitializer(Block block, int pos, ILVariable store, IType elementType, int length, out ILInstruction[] values, out int elementCount) { - instructionsToRemove = 0; - values = null; + elementCount = 0; values = new ILInstruction[length]; int nextMinimumIndex = 0; - int elementCount = 0; for (int i = pos; i < block.Instructions.Count; i++) { if (nextMinimumIndex >= length) break; @@ -180,7 +327,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms values[nextMinimumIndex] = value; nextMinimumIndex++; elementCount++; - instructionsToRemove++; } if (pos + elementCount >= block.Instructions.Count) return false;