From 0dac55d584bbb95ff973f045a1b05af00ac937a7 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 8 Dec 2018 22:31:17 +0100 Subject: [PATCH] Add transformation for stackalloc to Span. --- .../CSharp/ExpressionBuilder.cs | 44 ++++++++-- .../Transforms/IntroduceUnsafeModifier.cs | 2 +- ICSharpCode.Decompiler/IL/Instructions.cs | 81 +++++++++++++++++++ ICSharpCode.Decompiler/IL/Instructions.tt | 2 + .../IL/Transforms/ExpressionTransforms.cs | 73 ++++++++++++++++- .../IL/Transforms/ILInlining.cs | 2 + .../Transforms/TransformArrayInitializers.cs | 15 ++-- 7 files changed, 205 insertions(+), 14 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index b34ddbced..67c26d14d 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -287,6 +287,12 @@ namespace ICSharpCode.Decompiler.CSharp protected internal override TranslatedExpression VisitNewObj(NewObj inst, TranslationContext context) { + var type = inst.Method.DeclaringType; + if (type.IsKnownType(KnownTypeCode.SpanOfT) || type.IsKnownType(KnownTypeCode.ReadOnlySpanOfT)) { + if (inst.Arguments.Count == 2 && inst.Arguments[0] is Block b && b.Kind == BlockKind.StackAllocInitializer) { + return TranslateStackAllocInitializer(b, type.TypeArguments[0]); + } + } return new CallBuilder(this, typeSystem, settings).Build(inst); } @@ -310,6 +316,24 @@ namespace ICSharpCode.Decompiler.CSharp .WithILInstruction(inst).WithRR(new ResolveResult(new PointerType(elementType))); } + protected internal override TranslatedExpression VisitLocAllocSpan(LocAllocSpan inst, TranslationContext context) + { + return TranslateLocAllocSpan(inst, context.TypeHint, out var elementType) + .WithILInstruction(inst).WithRR(new ResolveResult(inst.Type)); + } + + StackAllocExpression TranslateLocAllocSpan(LocAllocSpan inst, IType typeHint, out IType elementType) + { + elementType = inst.Type.TypeArguments[0]; + PointerType pointerType = new PointerType(elementType); + TranslatedExpression countExpression = Translate(inst.Argument) + .ConvertTo(compilation.FindType(KnownTypeCode.Int32), this); + return new StackAllocExpression { + Type = ConvertType(elementType), + CountExpression = countExpression + }; + } + StackAllocExpression TranslateLocAlloc(LocAlloc inst, IType typeHint, out IType elementType) { TranslatedExpression countExpression; @@ -2258,13 +2282,20 @@ namespace ICSharpCode.Decompiler.CSharp 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); + StackAllocExpression stackAllocExpression; + IType elementType; + switch (stloc.Value) { + case LocAlloc locAlloc: + stackAllocExpression = TranslateLocAlloc(locAlloc, typeHint, out elementType); + break; + case LocAllocSpan locAllocSpan: + stackAllocExpression = TranslateLocAllocSpan(locAllocSpan, typeHint, out elementType); + break; + default: + throw new ArgumentException("given Block is invalid!"); + } var initializer = stackAllocExpression.Initializer = new ArrayInitializerExpression(); var pointerType = new PointerType(elementType); - long expectedOffset = 0; for (int i = 1; i < block.Instructions.Count; i++) { @@ -2272,10 +2303,13 @@ namespace ICSharpCode.Decompiler.CSharp 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!"); long offset = 0; + target = target.UnwrapConv(ConversionKind.StopGCTracking); + if (!target.MatchLdLoc(stloc.Variable)) { if (!target.MatchBinaryNumericInstruction(BinaryNumericOperator.Add, out var left, out var right)) throw new ArgumentException("given Block is invalid!"); var binary = (BinaryNumericInstruction)target; + left = left.UnwrapConv(ConversionKind.StopGCTracking); var offsetInst = PointerArithmeticOffset.Detect(right, pointerType, binary.CheckForOverflow); if (!left.MatchLdLoc(final.Variable) || offsetInst == null) throw new ArgumentException("given Block is invalid!"); diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs index 6424ebdd5..a9c786215 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs @@ -142,7 +142,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms public override bool VisitStackAllocExpression(StackAllocExpression stackAllocExpression) { base.VisitStackAllocExpression(stackAllocExpression); - return true; + return stackAllocExpression.GetResolveResult().Type is PointerType; } public override bool VisitInvocationExpression(InvocationExpression invocationExpression) diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs index 87a1c4d88..a8a530531 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions.cs @@ -141,6 +141,8 @@ namespace ICSharpCode.Decompiler.IL LdMemberToken, /// Allocates space in the stack frame LocAlloc, + /// Allocates space in the stack frame and wraps it in a Span + LocAllocSpan, /// memcpy(destAddress, sourceAddress, size); Cpblk, /// memset(address, value, size) @@ -3192,6 +3194,60 @@ namespace ICSharpCode.Decompiler.IL } } namespace ICSharpCode.Decompiler.IL +{ + /// Allocates space in the stack frame and wraps it in a Span + public sealed partial class LocAllocSpan : UnaryInstruction + { + public LocAllocSpan(ILInstruction argument, IType type) : base(OpCode.LocAllocSpan, argument) + { + this.type = type; + } + IType type; + /// Returns the type operand. + public IType Type { + get { return type; } + set { type = value; InvalidateFlags(); } + } + public override StackType ResultType { get { return StackType.O; } } + protected override InstructionFlags ComputeFlags() + { + return base.ComputeFlags() | InstructionFlags.MayThrow; + } + public override InstructionFlags DirectFlags { + get { + return base.DirectFlags | InstructionFlags.MayThrow; + } + } + public override void WriteTo(ITextOutput output, ILAstWritingOptions options) + { + ILRange.WriteTo(output, options); + output.Write(OpCode); + output.Write(' '); + type.WriteTo(output); + output.Write('('); + Argument.WriteTo(output, options); + output.Write(')'); + } + public override void AcceptVisitor(ILVisitor visitor) + { + visitor.VisitLocAllocSpan(this); + } + public override T AcceptVisitor(ILVisitor visitor) + { + return visitor.VisitLocAllocSpan(this); + } + public override T AcceptVisitor(ILVisitor visitor, C context) + { + return visitor.VisitLocAllocSpan(this, context); + } + protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match) + { + var o = other as LocAllocSpan; + return o != null && this.Argument.PerformMatch(o.Argument, ref match) && type.Equals(o.type); + } + } +} +namespace ICSharpCode.Decompiler.IL { /// memcpy(destAddress, sourceAddress, size); public sealed partial class Cpblk : ILInstruction, ISupportsVolatilePrefix, ISupportsUnalignedPrefix @@ -6497,6 +6553,10 @@ namespace ICSharpCode.Decompiler.IL { Default(inst); } + protected internal virtual void VisitLocAllocSpan(LocAllocSpan inst) + { + Default(inst); + } protected internal virtual void VisitCpblk(Cpblk inst) { Default(inst); @@ -6875,6 +6935,10 @@ namespace ICSharpCode.Decompiler.IL { return Default(inst); } + protected internal virtual T VisitLocAllocSpan(LocAllocSpan inst) + { + return Default(inst); + } protected internal virtual T VisitCpblk(Cpblk inst) { return Default(inst); @@ -7253,6 +7317,10 @@ namespace ICSharpCode.Decompiler.IL { return Default(inst, context); } + protected internal virtual T VisitLocAllocSpan(LocAllocSpan inst, C context) + { + return Default(inst, context); + } protected internal virtual T VisitCpblk(Cpblk inst, C context) { return Default(inst, context); @@ -7470,6 +7538,7 @@ namespace ICSharpCode.Decompiler.IL "ldtypetoken", "ldmembertoken", "localloc", + "localloc.span", "cpblk", "initblk", "ldflda", @@ -7816,6 +7885,18 @@ namespace ICSharpCode.Decompiler.IL argument = default(ILInstruction); return false; } + public bool MatchLocAllocSpan(out ILInstruction argument, out IType type) + { + var inst = this as LocAllocSpan; + if (inst != null) { + argument = inst.Argument; + type = inst.Type; + return true; + } + argument = default(ILInstruction); + type = default(IType); + return false; + } public bool MatchCpblk(out ILInstruction destAddress, out ILInstruction sourceAddress, out ILInstruction size) { var inst = this as Cpblk; diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt index fdfeaaab9..ec97f65ac 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.tt +++ b/ICSharpCode.Decompiler/IL/Instructions.tt @@ -218,6 +218,8 @@ CustomClassName("LdMemberToken"), NoArguments, HasMemberOperand, ResultType("O")), new OpCode("localloc", "Allocates space in the stack frame", CustomClassName("LocAlloc"), Unary, ResultType("I"), MayThrow), + new OpCode("localloc.span", "Allocates space in the stack frame and wraps it in a Span", + CustomClassName("LocAllocSpan"), Unary, HasTypeOperand, ResultType("O"), MayThrow), new OpCode("cpblk", "memcpy(destAddress, sourceAddress, size);", CustomArguments(("destAddress", new[] { "I", "Ref" }), ("sourceAddress", new[] { "I", "Ref" }), ("size", new[] { "I4" })), MayThrow, MemoryAccess, diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index 2baede38e..b78ff84f0 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -262,15 +262,82 @@ namespace ICSharpCode.Decompiler.IL.Transforms protected internal override void VisitNewObj(NewObj inst) { - LdcDecimal decimalConstant; - if (TransformDecimalCtorToConstant(inst, out decimalConstant)) { + if (TransformDecimalCtorToConstant(inst, out LdcDecimal decimalConstant)) { context.Step("TransformDecimalCtorToConstant", inst); inst.ReplaceWith(decimalConstant); return; } + if (TransformSpanTCtorContainingStackAlloc(inst, out ILInstruction locallocSpan)) { + inst.ReplaceWith(locallocSpan); + Block block = null; + ILInstruction stmt = locallocSpan; + while (stmt.Parent != null) { + if (stmt.Parent is Block b) { + block = b; + break; + } + stmt = stmt.Parent; + } + //ILInlining.InlineIfPossible(block, stmt.ChildIndex - 1, context); + return; + } base.VisitNewObj(inst); } - + + /// + /// newobj Span..ctor(localloc(conv i4->u <zero extend>(ldc.i4 sizeInBytes)), numberOfElementsExpr) + /// => + /// localloc.span T(numberOfElementsExpr) + /// + /// -or- + /// + /// newobj Span..ctor(Block IL_0000 (StackAllocInitializer) { + /// stloc I_0(localloc(conv i4->u<zero extend>(ldc.i4 sizeInBytes))) + /// ... + /// final: ldloc I_0 + /// }, numberOfElementsExpr) + /// => + /// Block IL_0000 (StackAllocInitializer) { + /// stloc I_0(localloc.span T(numberOfElementsExpr)) + /// ... + /// final: ldloc I_0 + /// } + /// + bool TransformSpanTCtorContainingStackAlloc(NewObj newObj, out ILInstruction locallocSpan) + { + locallocSpan = 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; + if (newObj.Arguments[0] is LocAlloc) { + locallocSpan = new LocAllocSpan(newObj.Arguments[1], type); + return true; + } + if (newObj.Arguments[0] is Block initializer && initializer.Kind == BlockKind.StackAllocInitializer) { + if (!initializer.Instructions[0].MatchStLoc(out var initializerVariable, out var value)) + return false; + if (!value.MatchLocAlloc(out _)) + return false; + var newVariable = initializerVariable.Function.RegisterVariable(VariableKind.InitializerTarget, type); + foreach (var load in initializerVariable.LoadInstructions.ToArray()) { + ILInstruction newInst = new LdLoc(newVariable); + newInst.AddILRange(load.ILRange); + if (load.Parent != initializer) + newInst = new Conv(newInst, PrimitiveType.I, false, Sign.None); + load.ReplaceWith(newInst); + } + foreach (var store in initializerVariable.StoreInstructions.ToArray()) { + store.Variable = newVariable; + } + value.ReplaceWith(new LocAllocSpan(newObj.Arguments[1], type)); + locallocSpan = initializer; + return true; + } + return false; + } + bool TransformDecimalCtorToConstant(NewObj inst, out LdcDecimal result) { IType t = inst.Method.DeclaringType; diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index 704fcb808..082344bf2 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -359,6 +359,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (parent.Parent.OpCode == OpCode.DynamicCompoundAssign) return true; // inline into dynamic compound assignments break; + case OpCode.LocAllocSpan: + return true; // inline size-expressions into localloc.span } // decide based on the target into which we are inlining switch (next.OpCode) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs index 09938873c..4fafd255f 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs @@ -128,7 +128,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms 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))); + block.Instructions.Add(new StLoc(tempStore, locallocExpr)); while (blob.RemainingBytes > 0) { block.Instructions.Add(StElemPtr(tempStore, blob.Offset, new LdcI4(blob.ReadByte()), elementType)); @@ -138,18 +138,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms 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, lengthInst, out elementType, out StObj[] values, out int instructionsToRemove)) { + 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, new LocAlloc(lengthInst))); + 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; } } @@ -178,7 +180,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } - bool HandleSequentialLocAllocInitializer(Block block, int pos, ILVariable store, ILInstruction lengthInstruction, out IType elementType, out StObj[] values, out int instructionsToRemove) + 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; @@ -186,10 +188,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms 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) || !value.MatchLdcI4(0) || !size.MatchLdcI(byteCount)) + if (!dest.MatchLdLoc(store) || !size.MatchLdcI(byteCount)) return false; instructionsToRemove++; pos++;