Browse Source

Merge pull request #1246 from siegfriedpammer/cs73-stackalloc-initializers

C# 7.3 stackalloc initializers
pull/1347/head
Siegfried Pammer 8 years ago committed by GitHub
parent
commit
457813bac7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
  2. 2
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  3. 6
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  4. 392
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs
  5. 1249
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.opt.roslyn.il
  6. 1406
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.roslyn.il
  7. 95
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  8. 1
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  9. 4
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  10. 5
      ICSharpCode.Decompiler/CSharp/Syntax/Expressions/ArrayCreateExpression.cs
  11. 10
      ICSharpCode.Decompiler/CSharp/Syntax/Expressions/StackAllocExpression.cs
  12. 7
      ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs
  13. 18
      ICSharpCode.Decompiler/DecompilerSettings.cs
  14. 4
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  15. 81
      ICSharpCode.Decompiler/IL/Instructions.cs
  16. 2
      ICSharpCode.Decompiler/IL/Instructions.tt
  17. 1
      ICSharpCode.Decompiler/IL/Instructions/Block.cs
  18. 81
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  19. 2
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  20. 361
      ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs

5
ICSharpCode.Decompiler.Tests/Helpers/Tester.cs

@ -186,9 +186,11 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
MetadataReference.CreateFromFile(Path.Combine(refAsmPath, "mscorlib.dll")), MetadataReference.CreateFromFile(Path.Combine(refAsmPath, "mscorlib.dll")),
MetadataReference.CreateFromFile(Path.Combine(refAsmPath, "System.dll")), MetadataReference.CreateFromFile(Path.Combine(refAsmPath, "System.dll")),
MetadataReference.CreateFromFile(Path.Combine(refAsmPath, "System.Core.dll")), MetadataReference.CreateFromFile(Path.Combine(refAsmPath, "System.Core.dll")),
MetadataReference.CreateFromFile(Path.Combine(refAsmPath, @"Facades\System.Runtime.dll")),
MetadataReference.CreateFromFile(Path.Combine(refAsmPath, "System.Xml.dll")), MetadataReference.CreateFromFile(Path.Combine(refAsmPath, "System.Xml.dll")),
MetadataReference.CreateFromFile(Path.Combine(refAsmPath, "Microsoft.CSharp.dll")), MetadataReference.CreateFromFile(Path.Combine(refAsmPath, "Microsoft.CSharp.dll")),
MetadataReference.CreateFromFile(typeof(ValueTuple).Assembly.Location) MetadataReference.CreateFromFile(typeof(ValueTuple).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Span<>).Assembly.Location),
}; };
}); });
@ -401,6 +403,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
var module = new PEFile(assemblyFileName, file, PEStreamOptions.PrefetchEntireImage); var module = new PEFile(assemblyFileName, file, PEStreamOptions.PrefetchEntireImage);
var resolver = new UniversalAssemblyResolver(assemblyFileName, false, var resolver = new UniversalAssemblyResolver(assemblyFileName, false,
module.Reader.DetectTargetFrameworkId(), PEStreamOptions.PrefetchMetadata); module.Reader.DetectTargetFrameworkId(), PEStreamOptions.PrefetchMetadata);
resolver.AddSearchDirectory(Path.GetDirectoryName(typeof(Span<>).Assembly.Location));
var typeSystem = new DecompilerTypeSystem(module, resolver, settings); var typeSystem = new DecompilerTypeSystem(module, resolver, settings);
CSharpDecompiler decompiler = new CSharpDecompiler(typeSystem, settings); CSharpDecompiler decompiler = new CSharpDecompiler(typeSystem, settings);
decompiler.AstTransforms.Insert(0, new RemoveEmbeddedAtttributes()); decompiler.AstTransforms.Insert(0, new RemoveEmbeddedAtttributes());

2
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -42,6 +42,7 @@
<PackageReference Include="NUnit3TestAdapter" Version="3.11.2" /> <PackageReference Include="NUnit3TestAdapter" Version="3.11.2" />
<PackageReference Include="System.Collections.Immutable" Version="1.5.0" /> <PackageReference Include="System.Collections.Immutable" Version="1.5.0" />
<PackageReference Include="NUnit" Version="3.11.0" /> <PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="System.Memory" Version="4.5.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -72,6 +73,7 @@
<Compile Include="TestCases\Correctness\RefLocalsAndReturns.cs" /> <Compile Include="TestCases\Correctness\RefLocalsAndReturns.cs" />
<Compile Include="TestCases\ILPretty\Issue1256.cs" /> <Compile Include="TestCases\ILPretty\Issue1256.cs" />
<Compile Include="TestCases\ILPretty\Issue1323.cs" /> <Compile Include="TestCases\ILPretty\Issue1323.cs" />
<Compile Include="TestCases\Pretty\CS73_StackAllocInitializers.cs" />
<Compile Include="TestCases\Pretty\OptionalArguments.cs" /> <Compile Include="TestCases\Pretty\OptionalArguments.cs" />
<Compile Include="TestCases\Pretty\CustomShortCircuitOperators.cs" /> <Compile Include="TestCases\Pretty\CustomShortCircuitOperators.cs" />
<Compile Include="TestCases\Pretty\ReduceNesting.cs" /> <Compile Include="TestCases\Pretty\ReduceNesting.cs" />

6
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -287,6 +287,12 @@ namespace ICSharpCode.Decompiler.Tests
Run(cscOptions: cscOptions); Run(cscOptions: cscOptions);
} }
[Test]
public void CS73_StackAllocInitializers([ValueSource(nameof(roslynOnlyOptions))] CSharpCompilerOptions cscOptions)
{
RunForLibrary(cscOptions: cscOptions);
}
[Test] [Test]
public void RefLocalsAndReturns([ValueSource(nameof(roslynOnlyOptions))] CSharpCompilerOptions cscOptions) public void RefLocalsAndReturns([ValueSource(nameof(roslynOnlyOptions))] CSharpCompilerOptions cscOptions)
{ {

392
ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs

@ -0,0 +1,392 @@
// Copyright (c) 2018 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.Runtime.InteropServices;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
internal class CS73_StackAllocInitializers
{
[StructLayout(LayoutKind.Sequential, Size = 5)]
private struct StructWithSize5
{
public byte a;
public byte b;
public byte c;
public byte d;
public byte e;
public StructWithSize5(byte a, byte b, byte c, byte d, byte e)
{
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.e = e;
}
}
public unsafe string SimpleStackAllocStruct1()
{
StructWithSize5* ptr = stackalloc StructWithSize5[4] {
new StructWithSize5(1, 2, 3, 4, 5),
new StructWithSize5(11, 22, 33, 44, 55),
new StructWithSize5(1, 4, 8, 6, 2),
new StructWithSize5(12, 23, 34, 45, 56)
};
Console.WriteLine(*ptr);
return UsePointer((byte*)ptr);
}
public unsafe string SimpleStackAllocBool()
{
bool* ptr = stackalloc bool[4] {
false,
true,
false,
true
};
Console.WriteLine(*ptr);
return UsePointer((byte*)ptr);
}
public unsafe string DoNotInlineTest()
{
bool* ptr = stackalloc bool[4] {
false,
true,
false,
true
};
return UsePointer((byte*)ptr);
}
public unsafe string SimpleStackAllocByte()
{
byte* ptr = stackalloc byte[2] {
0,
1
};
Console.WriteLine(*ptr);
return UsePointer(ptr);
}
public unsafe string SimpleStackAllocPrimesAsBytes()
{
byte* ptr = stackalloc byte[55] {
1,
2,
3,
5,
7,
11,
13,
17,
19,
23,
29,
31,
37,
41,
43,
47,
53,
59,
61,
67,
71,
73,
79,
83,
89,
97,
101,
103,
107,
109,
113,
127,
131,
137,
139,
149,
151,
157,
163,
167,
173,
179,
181,
191,
193,
197,
199,
211,
223,
227,
229,
233,
239,
241,
251
};
Console.WriteLine(*ptr);
return UsePointer(ptr);
}
public unsafe string SimpleStackAllocChar()
{
char* ptr = stackalloc char[4] {
'1',
'2',
'3',
'4'
};
Console.WriteLine(*ptr);
return UsePointer((byte*)ptr);
}
public unsafe string SimpleStackAllocCharAlphabet()
{
char* ptr = stackalloc char[26] {
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z'
};
Console.WriteLine(*ptr);
return UsePointer((byte*)ptr);
}
public unsafe string SimpleStackAllocSByte()
{
sbyte* ptr = stackalloc sbyte[3] {
1,
2,
3
};
Console.WriteLine(*ptr);
return UsePointer((byte*)ptr);
}
public unsafe string SimpleStackAllocInt16()
{
short* ptr = stackalloc short[3] {
1,
2,
3
};
Console.WriteLine(*ptr);
return UsePointer((byte*)ptr);
}
public unsafe string SimpleStackAllocUInt16()
{
ushort* ptr = stackalloc ushort[3] {
1,
2,
3
};
Console.WriteLine(*ptr);
return UsePointer((byte*)ptr);
}
public unsafe string SimpleStackAllocInt32()
{
int* ptr = stackalloc int[3] {
1,
2,
3
};
Console.WriteLine(*ptr);
return UsePointer((byte*)ptr);
}
public unsafe string SimpleStackAllocInt32(int a, int b, int c)
{
int* ptr = stackalloc int[6] {
1,
a,
2,
b,
3,
c
};
Console.WriteLine(*ptr);
return UsePointer((byte*)ptr);
}
public unsafe string SimpleStackAllocInt32Fibonacci()
{
int* ptr = stackalloc int[17] {
1,
1,
2,
3,
5,
8,
13,
21,
34,
55,
89,
144,
233,
377,
610,
987,
1597
};
Console.WriteLine(*ptr);
return UsePointer((byte*)ptr);
}
public unsafe string SimpleStackAllocUInt32()
{
uint* ptr = stackalloc uint[3] {
1u,
2u,
3u
};
Console.WriteLine(*ptr);
return UsePointer((byte*)ptr);
}
public unsafe string SimpleStackAllocInt64()
{
long* ptr = stackalloc long[3] {
1L,
2L,
3L
};
Console.WriteLine(*ptr);
return UsePointer((byte*)ptr);
}
public unsafe string SimpleStackAllocUInt64()
{
ulong* ptr = stackalloc ulong[3] {
1uL,
2uL,
3uL
};
Console.WriteLine(*ptr);
return UsePointer((byte*)ptr);
}
public unsafe string SimpleStackAllocInt32NonConstant(int a, int b, int c)
{
int* ptr = stackalloc int[6] {
0,
1,
0,
a,
b,
c
};
Console.WriteLine(*ptr);
return UsePointer((byte*)ptr);
}
public unsafe string NotAnInitializer(int a, int b, int c)
{
int* ptr = stackalloc int[6];
ptr[1] = a;
ptr[3] = b;
ptr[5] = c;
Console.WriteLine(*ptr);
return UsePointer((byte*)ptr);
}
public unsafe string NegativeOffsets(int a, int b, int c)
{
#if OPT
byte* intPtr = stackalloc byte[12];
*(int*)intPtr = 1;
*(int*)(intPtr - 4) = 2;
*(int*)(intPtr - 8) = 3;
int* ptr = (int*)intPtr;
Console.WriteLine(*ptr);
return UsePointer((byte*)ptr);
#else
byte* ptr = stackalloc byte[12];
*(int*)ptr = 1;
*(int*)(ptr - 4) = 2;
*(int*)(ptr - 8) = 3;
int* ptr2 = (int*)ptr;
Console.WriteLine(*ptr2);
return UsePointer((byte*)ptr2);
#endif
}
public unsafe string UsePointer(byte* ptr)
{
return ptr->ToString();
}
public string GetSpan()
{
Span<int> span = stackalloc int[GetSize()];
return UseSpan(span);
}
public string GetSpan2()
{
Span<int> span = stackalloc int[4] {
1,
2,
3,
4
};
return UseSpan(span);
}
public string UseSpan(Span<int> span)
{
throw new NotImplementedException();
}
public int GetSize()
{
throw new NotImplementedException();
}
}
}

1249
ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.opt.roslyn.il

File diff suppressed because it is too large Load Diff

1406
ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.roslyn.il

File diff suppressed because it is too large Load Diff

95
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -287,6 +287,12 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override TranslatedExpression VisitNewObj(NewObj inst, TranslationContext context) 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); return new CallBuilder(this, typeSystem, settings).Build(inst);
} }
@ -305,18 +311,42 @@ namespace ICSharpCode.Decompiler.CSharp
} }
protected internal override TranslatedExpression VisitLocAlloc(LocAlloc inst, TranslationContext context) 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)));
}
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; TranslatedExpression countExpression;
PointerType pointerType; PointerType pointerType;
if (inst.Argument.MatchBinaryNumericInstruction(BinaryNumericOperator.Mul, out var left, out var right) 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 // Determine the element type from the sizeof
countExpression = Translate(left.UnwrapConv(ConversionKind.ZeroExtend)); countExpression = Translate(left.UnwrapConv(ConversionKind.ZeroExtend));
pointerType = new PointerType(elementType); pointerType = new PointerType(elementType);
} else { } else {
// Determine the element type from the expected pointer type in this context // 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( if (pointerType != null && GetPointerArithmeticOffset(
inst.Argument, Translate(inst.Argument), inst.Argument, Translate(inst.Argument),
pointerType, checkForOverflow: true, pointerType, checkForOverflow: true,
@ -335,7 +365,7 @@ namespace ICSharpCode.Decompiler.CSharp
return new StackAllocExpression { return new StackAllocExpression {
Type = ConvertType(elementType), Type = ConvertType(elementType),
CountExpression = countExpression CountExpression = countExpression
}.WithILInstruction(inst).WithRR(new ResolveResult(new PointerType(elementType))); };
} }
protected internal override TranslatedExpression VisitLdcI4(LdcI4 inst, TranslationContext context) protected internal override TranslatedExpression VisitLdcI4(LdcI4 inst, TranslationContext context)
@ -1993,6 +2023,8 @@ namespace ICSharpCode.Decompiler.CSharp
switch (block.Kind) { switch (block.Kind) {
case BlockKind.ArrayInitializer: case BlockKind.ArrayInitializer:
return TranslateArrayInitializer(block); return TranslateArrayInitializer(block);
case BlockKind.StackAllocInitializer:
return TranslateStackAllocInitializer(block, context.TypeHint);
case BlockKind.CollectionInitializer: case BlockKind.CollectionInitializer:
case BlockKind.ObjectInitializer: case BlockKind.ObjectInitializer:
return TranslateObjectAndCollectionInitializer(block); return TranslateObjectAndCollectionInitializer(block);
@ -2247,6 +2279,63 @@ namespace ICSharpCode.Decompiler.CSharp
.WithRR(new ArrayCreateResolveResult(new ArrayType(compilation, type, dimensions), newArr.Indices.Select(i => Translate(i).ResolveResult).ToArray(), elementResolveResults)); .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!");
StackAllocExpression stackAllocExpression;
IType elementType;
if (block.Instructions.Count < 2 || !block.Instructions[1].MatchStObj(out _, out _, out var t))
throw new ArgumentException("given Block is invalid!");
if (typeHint is PointerType pt && !TypeUtils.IsCompatibleTypeForMemoryAccess(t, pt.ElementType)) {
typeHint = new PointerType(t);
}
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++) {
// stobj type(binary.add.i(ldloc I_0, conv i4->i <sign extend> (ldc.i4 offset)), value)
if (!block.Instructions[i].MatchStObj(out var target, out var value, out 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!");
if (!offsetInst.MatchLdcI(out offset))
throw new ArgumentException("given Block is invalid!");
}
while (expectedOffset < offset) {
initializer.Elements.Add(Translate(IL.Transforms.TransformArrayInitializers.GetNullExpression(elementType), typeHint: elementType));
expectedOffset++;
}
var val = Translate(value, typeHint: elementType).ConvertTo(elementType, this, allowImplicitConversion: true);
initializer.Elements.Add(val);
expectedOffset++;
}
return stackAllocExpression.WithILInstruction(block)
.WithRR(new ResolveResult(stloc.Variable.Type));
}
TranslatedExpression TranslatePostfixOperator(Block block) TranslatedExpression TranslatePostfixOperator(Block block)
{ {
var targetInst = (block.Instructions.ElementAtOrDefault(0) as StLoc)?.Value; var targetInst = (block.Instructions.ElementAtOrDefault(0) as StLoc)?.Value;

1
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs

@ -1048,6 +1048,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
WriteKeyword(StackAllocExpression.StackallocKeywordRole); WriteKeyword(StackAllocExpression.StackallocKeywordRole);
stackAllocExpression.Type.AcceptVisitor(this); stackAllocExpression.Type.AcceptVisitor(this);
WriteCommaSeparatedListInBrackets(new[] { stackAllocExpression.CountExpression }); WriteCommaSeparatedListInBrackets(new[] { stackAllocExpression.CountExpression });
stackAllocExpression.Initializer.AcceptVisitor(this);
EndNode(stackAllocExpression); EndNode(stackAllocExpression);
} }

4
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -965,7 +965,7 @@ namespace ICSharpCode.Decompiler.CSharp
exprBuilder.Translate(inst.Size) exprBuilder.Translate(inst.Size)
} }
}); });
stmt.AddChild(new Comment(" IL initblk instruction"), Roles.Comment); stmt.InsertChildAfter(null, new Comment(" IL initblk instruction"), Roles.Comment);
return stmt; return stmt;
} }
@ -979,7 +979,7 @@ namespace ICSharpCode.Decompiler.CSharp
exprBuilder.Translate(inst.Size) exprBuilder.Translate(inst.Size)
} }
}); });
stmt.AddChild(new Comment(" IL cpblk instruction"), Roles.Comment); stmt.InsertChildAfter(null, new Comment(" IL cpblk instruction"), Roles.Comment);
return stmt; return stmt;
} }
} }

5
ICSharpCode.Decompiler/CSharp/Syntax/Expressions/ArrayCreateExpression.cs

@ -72,7 +72,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
protected internal override bool DoMatch(AstNode other, PatternMatching.Match match) protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
{ {
ArrayCreateExpression o = other as ArrayCreateExpression; ArrayCreateExpression o = other as ArrayCreateExpression;
return o != null && this.Type.DoMatch(o.Type, match) && this.Arguments.DoMatch(o.Arguments, match) && this.AdditionalArraySpecifiers.DoMatch(o.AdditionalArraySpecifiers, match) && this.Initializer.DoMatch(o.Initializer, match); return o != null && this.Type.DoMatch(o.Type, match)
&& this.Arguments.DoMatch(o.Arguments, match)
&& this.AdditionalArraySpecifiers.DoMatch(o.AdditionalArraySpecifiers, match)
&& this.Initializer.DoMatch(o.Initializer, match);
} }
} }
} }

10
ICSharpCode.Decompiler/CSharp/Syntax/Expressions/StackAllocExpression.cs

@ -33,6 +33,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
public class StackAllocExpression : Expression public class StackAllocExpression : Expression
{ {
public readonly static TokenRole StackallocKeywordRole = new TokenRole ("stackalloc"); public readonly static TokenRole StackallocKeywordRole = new TokenRole ("stackalloc");
public readonly static Role<ArrayInitializerExpression> InitializerRole = new Role<ArrayInitializerExpression>("Initializer", ArrayInitializerExpression.Null);
public CSharpTokenNode StackAllocToken { public CSharpTokenNode StackAllocToken {
get { return GetChildByRole (StackallocKeywordRole); } get { return GetChildByRole (StackallocKeywordRole); }
@ -56,6 +57,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
get { return GetChildByRole (Roles.RBracket); } get { return GetChildByRole (Roles.RBracket); }
} }
public ArrayInitializerExpression Initializer {
get { return GetChildByRole(InitializerRole); }
set { SetChildByRole(InitializerRole, value); }
}
public override void AcceptVisitor (IAstVisitor visitor) public override void AcceptVisitor (IAstVisitor visitor)
{ {
visitor.VisitStackAllocExpression (this); visitor.VisitStackAllocExpression (this);
@ -74,7 +80,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
protected internal override bool DoMatch(AstNode other, PatternMatching.Match match) protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
{ {
StackAllocExpression o = other as StackAllocExpression; StackAllocExpression o = other as StackAllocExpression;
return o != null && this.Type.DoMatch(o.Type, match) && this.CountExpression.DoMatch(o.CountExpression, match); return o != null && this.Type.DoMatch(o.Type, match)
&& this.CountExpression.DoMatch(o.CountExpression, match)
&& this.Initializer.DoMatch(o.Initializer, match);
} }
} }
} }

7
ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs

@ -141,8 +141,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
public override bool VisitStackAllocExpression(StackAllocExpression stackAllocExpression) public override bool VisitStackAllocExpression(StackAllocExpression stackAllocExpression)
{ {
base.VisitStackAllocExpression(stackAllocExpression); bool result = base.VisitStackAllocExpression(stackAllocExpression);
return true; var rr = stackAllocExpression.GetResolveResult();
if (rr?.Type is PointerType)
return true;
return result;
} }
public override bool VisitInvocationExpression(InvocationExpression invocationExpression) public override bool VisitInvocationExpression(InvocationExpression invocationExpression)

18
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -90,13 +90,14 @@ namespace ICSharpCode.Decompiler
} }
if (languageVersion < CSharp.LanguageVersion.CSharp7_3) { if (languageVersion < CSharp.LanguageVersion.CSharp7_3) {
//introduceUnmanagedTypeConstraint = false; //introduceUnmanagedTypeConstraint = false;
stackAllocInitializers = false;
tupleComparisons = false; tupleComparisons = false;
} }
} }
public CSharp.LanguageVersion GetMinimumRequiredVersion() public CSharp.LanguageVersion GetMinimumRequiredVersion()
{ {
if (tupleComparisons) if (tupleComparisons || stackAllocInitializers)
return CSharp.LanguageVersion.CSharp7_3; return CSharp.LanguageVersion.CSharp7_3;
if (introduceRefModifiersOnStructs || introduceReadonlyAndInModifiers || nonTrailingNamedArguments) if (introduceRefModifiersOnStructs || introduceReadonlyAndInModifiers || nonTrailingNamedArguments)
return CSharp.LanguageVersion.CSharp7_2; return CSharp.LanguageVersion.CSharp7_2;
@ -683,6 +684,21 @@ namespace ICSharpCode.Decompiler
} }
} }
bool stackAllocInitializers = true;
/// <summary>
/// Gets/Sets whether C# 7.3 stackalloc initializers should be used.
/// </summary>
public bool StackAllocInitializers {
get { return stackAllocInitializers; }
set {
if (stackAllocInitializers != value) {
stackAllocInitializers = value;
OnPropertyChanged();
}
}
}
bool tupleTypes = true; bool tupleTypes = true;
/// <summary> /// <summary>

4
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -74,9 +74,7 @@
<Compile Include="CSharp\Syntax\ErrorNode.cs" /> <Compile Include="CSharp\Syntax\ErrorNode.cs" />
<Compile Include="CSharp\Syntax\Expressions\AnonymousMethodExpression.cs" /> <Compile Include="CSharp\Syntax\Expressions\AnonymousMethodExpression.cs" />
<Compile Include="CSharp\Syntax\Expressions\AnonymousTypeCreateExpression.cs" /> <Compile Include="CSharp\Syntax\Expressions\AnonymousTypeCreateExpression.cs" />
<Compile Include="CSharp\Syntax\Expressions\ArrayCreateExpression.cs"> <Compile Include="CSharp\Syntax\Expressions\ArrayCreateExpression.cs" />
<DependentUpon>ArrayInitializerExpression.cs</DependentUpon>
</Compile>
<Compile Include="CSharp\Syntax\Expressions\ArrayInitializerExpression.cs" /> <Compile Include="CSharp\Syntax\Expressions\ArrayInitializerExpression.cs" />
<Compile Include="CSharp\Syntax\Expressions\AsExpression.cs" /> <Compile Include="CSharp\Syntax\Expressions\AsExpression.cs" />
<Compile Include="CSharp\Syntax\Expressions\AssignmentExpression.cs" /> <Compile Include="CSharp\Syntax\Expressions\AssignmentExpression.cs" />

81
ICSharpCode.Decompiler/IL/Instructions.cs

@ -141,6 +141,8 @@ namespace ICSharpCode.Decompiler.IL
LdMemberToken, LdMemberToken,
/// <summary>Allocates space in the stack frame</summary> /// <summary>Allocates space in the stack frame</summary>
LocAlloc, LocAlloc,
/// <summary>Allocates space in the stack frame and wraps it in a Span</summary>
LocAllocSpan,
/// <summary>memcpy(destAddress, sourceAddress, size);</summary> /// <summary>memcpy(destAddress, sourceAddress, size);</summary>
Cpblk, Cpblk,
/// <summary>memset(address, value, size)</summary> /// <summary>memset(address, value, size)</summary>
@ -3192,6 +3194,60 @@ namespace ICSharpCode.Decompiler.IL
} }
} }
namespace ICSharpCode.Decompiler.IL namespace ICSharpCode.Decompiler.IL
{
/// <summary>Allocates space in the stack frame and wraps it in a Span</summary>
public sealed partial class LocAllocSpan : UnaryInstruction
{
public LocAllocSpan(ILInstruction argument, IType type) : base(OpCode.LocAllocSpan, argument)
{
this.type = type;
}
IType type;
/// <summary>Returns the type operand.</summary>
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<T>(ILVisitor<T> visitor)
{
return visitor.VisitLocAllocSpan(this);
}
public override T AcceptVisitor<C, T>(ILVisitor<C, T> 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
{ {
/// <summary>memcpy(destAddress, sourceAddress, size);</summary> /// <summary>memcpy(destAddress, sourceAddress, size);</summary>
public sealed partial class Cpblk : ILInstruction, ISupportsVolatilePrefix, ISupportsUnalignedPrefix public sealed partial class Cpblk : ILInstruction, ISupportsVolatilePrefix, ISupportsUnalignedPrefix
@ -6497,6 +6553,10 @@ namespace ICSharpCode.Decompiler.IL
{ {
Default(inst); Default(inst);
} }
protected internal virtual void VisitLocAllocSpan(LocAllocSpan inst)
{
Default(inst);
}
protected internal virtual void VisitCpblk(Cpblk inst) protected internal virtual void VisitCpblk(Cpblk inst)
{ {
Default(inst); Default(inst);
@ -6875,6 +6935,10 @@ namespace ICSharpCode.Decompiler.IL
{ {
return Default(inst); return Default(inst);
} }
protected internal virtual T VisitLocAllocSpan(LocAllocSpan inst)
{
return Default(inst);
}
protected internal virtual T VisitCpblk(Cpblk inst) protected internal virtual T VisitCpblk(Cpblk inst)
{ {
return Default(inst); return Default(inst);
@ -7253,6 +7317,10 @@ namespace ICSharpCode.Decompiler.IL
{ {
return Default(inst, context); 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) protected internal virtual T VisitCpblk(Cpblk inst, C context)
{ {
return Default(inst, context); return Default(inst, context);
@ -7470,6 +7538,7 @@ namespace ICSharpCode.Decompiler.IL
"ldtypetoken", "ldtypetoken",
"ldmembertoken", "ldmembertoken",
"localloc", "localloc",
"localloc.span",
"cpblk", "cpblk",
"initblk", "initblk",
"ldflda", "ldflda",
@ -7816,6 +7885,18 @@ namespace ICSharpCode.Decompiler.IL
argument = default(ILInstruction); argument = default(ILInstruction);
return false; 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) public bool MatchCpblk(out ILInstruction destAddress, out ILInstruction sourceAddress, out ILInstruction size)
{ {
var inst = this as Cpblk; var inst = this as Cpblk;

2
ICSharpCode.Decompiler/IL/Instructions.tt

@ -218,6 +218,8 @@
CustomClassName("LdMemberToken"), NoArguments, HasMemberOperand, ResultType("O")), CustomClassName("LdMemberToken"), NoArguments, HasMemberOperand, ResultType("O")),
new OpCode("localloc", "Allocates space in the stack frame", new OpCode("localloc", "Allocates space in the stack frame",
CustomClassName("LocAlloc"), Unary, ResultType("I"), MayThrow), 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);", new OpCode("cpblk", "memcpy(destAddress, sourceAddress, size);",
CustomArguments(("destAddress", new[] { "I", "Ref" }), ("sourceAddress", new[] { "I", "Ref" }), ("size", new[] { "I4" })), CustomArguments(("destAddress", new[] { "I", "Ref" }), ("sourceAddress", new[] { "I", "Ref" }), ("size", new[] { "I4" })),
MayThrow, MemoryAccess, MayThrow, MemoryAccess,

1
ICSharpCode.Decompiler/IL/Instructions/Block.cs

@ -313,6 +313,7 @@ namespace ICSharpCode.Decompiler.IL
ArrayInitializer, ArrayInitializer,
CollectionInitializer, CollectionInitializer,
ObjectInitializer, ObjectInitializer,
StackAllocInitializer,
/// <summary> /// <summary>
/// Block is used for postfix operator on local variable. /// Block is used for postfix operator on local variable.
/// </summary> /// </summary>

81
ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

@ -262,15 +262,92 @@ namespace ICSharpCode.Decompiler.IL.Transforms
protected internal override void VisitNewObj(NewObj inst) protected internal override void VisitNewObj(NewObj inst)
{ {
LdcDecimal decimalConstant; if (TransformDecimalCtorToConstant(inst, out LdcDecimal decimalConstant)) {
if (TransformDecimalCtorToConstant(inst, out decimalConstant)) {
context.Step("TransformDecimalCtorToConstant", inst); context.Step("TransformDecimalCtorToConstant", inst);
inst.ReplaceWith(decimalConstant); inst.ReplaceWith(decimalConstant);
return; 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); base.VisitNewObj(inst);
} }
/// <summary>
/// newobj Span..ctor(localloc(conv i4->u &lt;zero extend&gt;(ldc.i4 sizeInBytes)), numberOfElementsExpr)
/// =>
/// localloc.span T(numberOfElementsExpr)
///
/// -or-
///
/// newobj Span..ctor(Block IL_0000 (StackAllocInitializer) {
/// stloc I_0(localloc(conv i4->u&lt;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
/// }
/// </summary>
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;
IType elementType = type.TypeArguments[0];
if (newObj.Arguments[0].MatchLocAlloc(out var sizeInBytes) && MatchesElementCount(sizeInBytes, elementType, newObj.Arguments[1])) {
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 sizeInBytes) && MatchesElementCount(sizeInBytes, elementType, newObj.Arguments[1])))
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 MatchesElementCount(ILInstruction sizeInBytesInstr, IType elementType, ILInstruction elementCountInstr2)
{
var pointerType = new PointerType(elementType);
var elementCountInstr = PointerArithmeticOffset.Detect(sizeInBytesInstr, pointerType, checkForOverflow: true, unwrapZeroExtension: true);
if (!elementCountInstr.Match(elementCountInstr2).Success)
return false;
return true;
}
bool TransformDecimalCtorToConstant(NewObj inst, out LdcDecimal result) bool TransformDecimalCtorToConstant(NewObj inst, out LdcDecimal result)
{ {
IType t = inst.Method.DeclaringType; IType t = inst.Method.DeclaringType;

2
ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

@ -360,6 +360,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (parent.Parent.OpCode == OpCode.DynamicCompoundAssign) if (parent.Parent.OpCode == OpCode.DynamicCompoundAssign)
return true; // inline into dynamic compound assignments return true; // inline into dynamic compound assignments
break; break;
case OpCode.LocAllocSpan:
return true; // inline size-expressions into localloc.span
} }
// decide based on the top-level target instruction into which we are inlining: // decide based on the top-level target instruction into which we are inlining:
switch (next.OpCode) { switch (next.OpCode) {

361
ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs

@ -39,8 +39,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return; return;
this.context = context; this.context = context;
try { try {
if (!DoTransform(block, pos)) if (DoTransform(block, pos))
DoTransformMultiDim(block, pos); return;
if (DoTransformMultiDim(block, pos))
return;
if (context.Settings.StackAllocInitializers && DoTransformStackAllocInitializer(block, pos))
return;
} finally { } finally {
this.context = null; this.context = null;
} }
@ -51,14 +55,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (pos >= body.Instructions.Count - 2) if (pos >= body.Instructions.Count - 2)
return false; return false;
ILInstruction inst = body.Instructions[pos]; ILInstruction inst = body.Instructions[pos];
ILVariable v; if (inst.MatchStLoc(out var v, out var newarrExpr) && MatchNewArr(newarrExpr, out var elementType, out var arrayLength)) {
ILInstruction newarrExpr; if (ForwardScanInitializeArrayRuntimeHelper(body, pos + 1, v, elementType, arrayLength, out var values, out var initArrayPos)) {
IType elementType;
int[] arrayLength;
if (inst.MatchStLoc(out v, out newarrExpr) && MatchNewArr(newarrExpr, out elementType, out arrayLength)) {
ILInstruction[] values;
int initArrayPos;
if (ForwardScanInitializeArrayRuntimeHelper(body, pos + 1, v, elementType, arrayLength, out values, out initArrayPos)) {
context.Step("ForwardScanInitializeArrayRuntimeHelper", inst); context.Step("ForwardScanInitializeArrayRuntimeHelper", inst);
var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type); var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type);
var block = BlockFromInitializer(tempStore, elementType, arrayLength, values); var block = BlockFromInitializer(tempStore, elementType, arrayLength, values);
@ -68,8 +66,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; return true;
} }
if (arrayLength.Length == 1) { if (arrayLength.Length == 1) {
int instructionsToRemove; if (HandleSimpleArrayInitializer(body, pos + 1, v, elementType, arrayLength[0], out values, out var instructionsToRemove)) {
if (HandleSimpleArrayInitializer(body, pos + 1, v, elementType, arrayLength[0], out values, out instructionsToRemove)) {
context.Step("HandleSimpleArrayInitializer", inst); context.Step("HandleSimpleArrayInitializer", inst);
var block = new Block(BlockKind.ArrayInitializer); var block = new Block(BlockKind.ArrayInitializer);
var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type); var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type);
@ -100,86 +97,205 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; return true;
} }
} }
// 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! return false;
// const int maxConsecutiveDefaultValueExpressions = 300; }
// var operands = new List<ILInstruction>();
// int numberOfInstructionsToRemove = 0;
// for (int j = pos + 1; j < body.Instructions.Count; j++) {
// var nextExpr = body.Instructions[j] as Void;
// int arrayPos;
// if (nextExpr != null && nextExpr is a.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;
// }
// }
bool DoTransformMultiDim(Block body, int pos)
{
if (pos >= body.Instructions.Count - 2)
return false;
ILInstruction instr = body.Instructions[pos];
if (instr.MatchStLoc(out var v, out var newarrExpr) && MatchNewArr(newarrExpr, out var arrayType, out var length)) {
if (ForwardScanInitializeArrayRuntimeHelper(body, pos + 1, v, arrayType, length, out var values, out var initArrayPos)) {
var block = BlockFromInitializer(v, arrayType, length, values);
body.Instructions[pos].ReplaceWith(new StLoc(v, block));
body.Instructions.RemoveAt(initArrayPos);
ILInlining.InlineIfPossible(body, pos, context);
return true;
}
} }
return false; return false;
} }
ILInstruction GetNullExpression(IType elementType) bool DoTransformStackAllocInitializer(Block body, int pos)
{ {
ITypeDefinition typeDef = elementType.GetEnumUnderlyingType().GetDefinition(); if (pos >= body.Instructions.Count - 2)
if (typeDef == null) return false;
return new DefaultValue(elementType); ILInstruction inst = body.Instructions[pos];
switch (typeDef.KnownTypeCode) { if (inst.MatchStLoc(out var v, out var locallocExpr) && locallocExpr.MatchLocAlloc(out var lengthInst)) {
case KnownTypeCode.Boolean: if (lengthInst.MatchLdcI(out var lengthInBytes) && HandleCpblkInitializer(body, pos + 1, v, lengthInBytes, out var blob, out var elementType)) {
case KnownTypeCode.Char: context.Step("HandleCpblkInitializer", inst);
case KnownTypeCode.SByte: var block = new Block(BlockKind.StackAllocInitializer);
case KnownTypeCode.Byte: var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new PointerType(elementType));
case KnownTypeCode.Int16: block.Instructions.Add(new StLoc(tempStore, locallocExpr));
case KnownTypeCode.UInt16:
case KnownTypeCode.Int32: while (blob.RemainingBytes > 0) {
case KnownTypeCode.UInt32: block.Instructions.Add(StElemPtr(tempStore, blob.Offset, new LdcI4(blob.ReadByte()), elementType));
return new LdcI4(0); }
case KnownTypeCode.Int64:
case KnownTypeCode.UInt64: block.FinalInstruction = new LdLoc(tempStore);
return new LdcI8(0); body.Instructions[pos] = new StLoc(v, block);
case KnownTypeCode.Single: body.Instructions.RemoveAt(pos + 1);
return new LdcF4(0); ILInlining.InlineIfPossible(body, pos, context);
case KnownTypeCode.Double: ExpressionTransforms.RunOnSingleStatement(body.Instructions[pos], context);
return new LdcF8(0); return true;
case KnownTypeCode.Decimal: }
return new LdcDecimal(0); if (HandleSequentialLocAllocInitializer(body, pos + 1, v, locallocExpr, out elementType, out StObj[] values, out int instructionsToRemove)) {
case KnownTypeCode.Void: context.Step("HandleSequentialLocAllocInitializer", inst);
throw new ArgumentException("void is not a valid element type!"); var block = new Block(BlockKind.StackAllocInitializer);
case KnownTypeCode.IntPtr: var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new PointerType(elementType));
case KnownTypeCode.UIntPtr: block.Instructions.Add(new StLoc(tempStore, locallocExpr));
default: block.Instructions.AddRange(values.Where(value => value != null).Select(value => RewrapStore(tempStore, value, elementType)));
return new DefaultValue(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 (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 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<IInstructionWithVariableOperand>().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, new PointerType(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, new PointerType(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);
} }
/// <summary> /// <summary>
/// Handle simple case where RuntimeHelpers.InitializeArray is not used. /// Handle simple case where RuntimeHelpers.InitializeArray is not used.
/// </summary> /// </summary>
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; elementCount = 0;
values = null;
values = new ILInstruction[length]; values = new ILInstruction[length];
int nextMinimumIndex = 0; int nextMinimumIndex = 0;
int elementCount = 0;
for (int i = pos; i < block.Instructions.Count; i++) { for (int i = pos; i < block.Instructions.Count; i++) {
if (nextMinimumIndex >= length) if (nextMinimumIndex >= length)
break; break;
if (!block.Instructions[i].MatchStObj(out ILInstruction target, out ILInstruction value, out IType type) || value.Descendants.OfType<IInstructionWithVariableOperand>().Any(inst => inst.Variable == store)) if (!block.Instructions[i].MatchStObj(out ILInstruction target, out ILInstruction value, out IType type)
|| value.Descendants.OfType<IInstructionWithVariableOperand>().Any(inst => inst.Variable == store))
{
break; break;
var ldelem = target as LdElema; }
if (ldelem == null || !ldelem.Array.MatchLdLoc(store) || ldelem.Indices.Count != 1 || !ldelem.Indices[0].MatchLdcI4(out int index))
if (!(target is LdElema ldelem) || !ldelem.Array.MatchLdLoc(store) || ldelem.Indices.Count != 1
|| !ldelem.Indices[0].MatchLdcI4(out int index))
{
break; break;
}
// index must be in range [0..length[ and must be greater than or equal to nextMinimumIndex // 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. // 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 // However, leaving array slots empty is allowed, as those are filled with default values when the
@ -190,7 +306,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
values[nextMinimumIndex] = value; values[nextMinimumIndex] = value;
nextMinimumIndex++; nextMinimumIndex++;
elementCount++; elementCount++;
instructionsToRemove++;
} }
if (pos + elementCount >= block.Instructions.Count) if (pos + elementCount >= block.Instructions.Count)
return false; return false;
@ -207,7 +322,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
IType type; IType type;
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
// 1. Instruction: (optional) temporary copy of store // 1. Instruction: (optional) temporary copy of store
bool hasTemporaryCopy = block.Instructions[pos].MatchStLoc(out ILVariable temp, out ILInstruction storeLoad) && storeLoad.MatchLdLoc(store); bool hasTemporaryCopy = block.Instructions[pos].MatchStLoc(out var temp, out var storeLoad) && storeLoad.MatchLdLoc(store);
if (hasTemporaryCopy) { if (hasTemporaryCopy) {
if (!MatchJaggedArrayStore(block, pos + 1, temp, i, out initializer, out type)) if (!MatchJaggedArrayStore(block, pos + 1, temp, i, out initializer, out type))
return false; return false;
@ -220,10 +335,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
pos += inc; pos += inc;
instructionsToRemove += inc; instructionsToRemove += inc;
} }
ILInstruction array;
// In case there is an extra copy of the store variable // In case there is an extra copy of the store variable
// Remove it and use its value instead. // Remove it and use its value instead.
if (block.Instructions[pos].MatchStLoc(out finalStore, out array)) { if (block.Instructions[pos].MatchStLoc(out finalStore, out var array)) {
instructionsToRemove++; instructionsToRemove++;
return array.MatchLdLoc(store); return array.MatchLdLoc(store);
} }
@ -237,39 +351,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms
type = null; type = null;
// 3. Instruction: stobj(ldelema(ldloc temp, ldc.i4 0), ldloc tempArrayLoad) // 3. Instruction: stobj(ldelema(ldloc temp, ldc.i4 0), ldloc tempArrayLoad)
var finalInstruction = block.Instructions.ElementAtOrDefault(pos + 1); var finalInstruction = block.Instructions.ElementAtOrDefault(pos + 1);
ILInstruction tempAccess, tempArrayLoad; if (finalInstruction == null || !finalInstruction.MatchStObj(out var tempAccess, out var tempArrayLoad, out type)
ILVariable initializerStore; || !tempArrayLoad.MatchLdLoc(out var initializerStore))
if (finalInstruction == null || !finalInstruction.MatchStObj(out tempAccess, out tempArrayLoad, out type) || !tempArrayLoad.MatchLdLoc(out initializerStore)) {
return false; return false;
var elemLoad = tempAccess as LdElema; }
if (elemLoad == null || !elemLoad.Array.MatchLdLoc(store) || elemLoad.Indices.Count != 1 || !elemLoad.Indices[0].MatchLdcI4(index)) if (!(tempAccess is LdElema elemLoad) || !elemLoad.Array.MatchLdLoc(store) || elemLoad.Indices.Count != 1
|| !elemLoad.Indices[0].MatchLdcI4(index))
{
return false; return false;
}
// 2. Instruction: stloc(temp) with block (array initializer) // 2. Instruction: stloc(temp) with block (array initializer)
var nextInstruction = block.Instructions.ElementAtOrDefault(pos); var nextInstruction = block.Instructions.ElementAtOrDefault(pos);
return nextInstruction != null && nextInstruction.MatchStLoc(initializerStore, out initializer) && initializer.OpCode == OpCode.Block; return nextInstruction != null
} && nextInstruction.MatchStLoc(initializerStore, out initializer)
&& initializer.OpCode == OpCode.Block;
bool DoTransformMultiDim(Block body, int pos)
{
if (pos >= body.Instructions.Count - 2)
return false;
ILVariable v;
ILInstruction newarrExpr;
IType arrayType;
int[] length;
ILInstruction instr = body.Instructions[pos];
if (instr.MatchStLoc(out v, out newarrExpr) && MatchNewArr(newarrExpr, out arrayType, out length)) {
ILInstruction[] values;
int initArrayPos;
if (ForwardScanInitializeArrayRuntimeHelper(body, pos + 1, v, arrayType, length, out values, out initArrayPos)) {
var block = BlockFromInitializer(v, arrayType, length, values);
body.Instructions[pos].ReplaceWith(new StLoc(v, block));
body.Instructions.RemoveAt(initArrayPos);
ILInlining.InlineIfPossible(body, pos, context);
return true;
}
}
return false;
} }
Block BlockFromInitializer(ILVariable v, IType elementType, int[] arrayLength, ILInstruction[] values) Block BlockFromInitializer(ILVariable v, IType elementType, int[] arrayLength, ILInstruction[] values)
@ -290,19 +386,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return block; return block;
} }
internal static bool MatchNewArr(ILInstruction instruction, out IType arrayType, out int[] length) static bool MatchNewArr(ILInstruction instruction, out IType arrayType, out int[] length)
{ {
NewArr newArr = instruction as NewArr;
length = null; length = null;
arrayType = null; arrayType = null;
if (newArr == null) if (!(instruction is NewArr newArr))
return false; return false;
arrayType = newArr.Type; arrayType = newArr.Type;
var args = newArr.Indices; var args = newArr.Indices;
length = new int[args.Count]; length = new int[args.Count];
for (int i = 0; i < args.Count; i++) { for (int i = 0; i < args.Count; i++) {
int value; if (!args[i].MatchLdcI4(out int value) || value <= 0) return false;
if (!args[i].MatchLdcI4(out value) || value <= 0) return false;
length[i] = value; length[i] = value;
} }
return true; return true;
@ -313,18 +407,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms
method = null; method = null;
array = null; array = null;
field = default; field = default;
Call call = instruction as Call; if (!(instruction is Call call) || call.Arguments.Count != 2)
if (call == null || call.Arguments.Count != 2)
return false; return false;
method = call.Method; method = call.Method;
if (method.DeclaringTypeDefinition == null || method.DeclaringTypeDefinition.FullName != "System.Runtime.CompilerServices.RuntimeHelpers") if (!method.IsStatic || method.Name != "InitializeArray" || method.DeclaringTypeDefinition == null)
return false; return false;
if (method.Name != "InitializeArray") var declaringType = method.DeclaringTypeDefinition;
if (declaringType.DeclaringType != null || declaringType.Name != "RuntimeHelpers"
|| declaringType.Namespace != "System.Runtime.CompilerServices")
{
return false; return false;
}
if (!call.Arguments[0].MatchLdLoc(out array)) if (!call.Arguments[0].MatchLdLoc(out array))
return false; return false;
IMember member; if (!call.Arguments[1].MatchLdMemberToken(out var member))
if (!call.Arguments[1].MatchLdMemberToken(out member))
return false; return false;
if (member.MetadataToken.IsNil) if (member.MetadataToken.IsNil)
return false; return false;
@ -415,6 +511,39 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return new StObj(new LdElema(type, array, indices), value, type); return new StObj(new LdElema(type, array, indices), 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) static int ElementSizeOf(TypeCode elementType)
{ {
switch (elementType) { switch (elementType) {

Loading…
Cancel
Save