Browse Source

Convert TransformAssignment into a statement transform and add support for inline property assignments.

pull/960/head
Daniel Grunwald 8 years ago
parent
commit
4c5f0b7e9c
  1. 21
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineAssignmentTest.cs
  2. 118
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineAssignmentTest.il
  3. 94
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineAssignmentTest.opt.il
  4. 7
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  5. 76
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs
  6. 15
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  7. 62
      ICSharpCode.Decompiler/IL/Instructions/Block.cs
  8. 2
      ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs
  9. 250
      ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs

21
ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineAssignmentTest.cs

@ -28,6 +28,15 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
private int[] field3; private int[] field3;
private short field4; private short field4;
public int InstanceProperty {
get;
set;
}
public static int StaticProperty {
get;
set;
}
public void SimpleInlineWithLocals() public void SimpleInlineWithLocals()
{ {
int value; int value;
@ -56,7 +65,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
this.UseShort(this.field4 = this.UseShort(0)); this.UseShort(this.field4 = this.UseShort(0));
Console.WriteLine(this.field4); Console.WriteLine(this.field4);
} }
public short UseShort(short s) public short UseShort(short s)
{ {
Console.WriteLine(s); Console.WriteLine(s);
@ -112,5 +121,15 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{ {
return this.GetArray()[this.GetIndex()] = this.GetValue(this.GetIndex()); return this.GetArray()[this.GetIndex()] = this.GetValue(this.GetIndex());
} }
public int StaticPropertyTest()
{
return InlineAssignmentTest.StaticProperty = this.GetIndex();
}
public int InstancePropertyTest()
{
return this.InstanceProperty = this.GetIndex();
}
} }
} }

118
ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineAssignmentTest.il

@ -10,7 +10,7 @@
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 4:0:0:0 .ver 4:0:0:0
} }
.assembly w1xqybg1 .assembly dcbsz5ie
{ {
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx
@ -20,15 +20,15 @@
.hash algorithm 0x00008004 .hash algorithm 0x00008004
.ver 0:0:0:0 .ver 0:0:0:0
} }
.module w1xqybg1.dll .module dcbsz5ie.dll
// MVID: {1BE5829A-0D5E-43DC-8C02-E3012B1780A2} // MVID: {CFA464E9-510B-40EC-AD90-3FB26B76D1A6}
.custom instance void [mscorlib]System.Security.UnverifiableCodeAttribute::.ctor() = ( 01 00 00 00 ) .custom instance void [mscorlib]System.Security.UnverifiableCodeAttribute::.ctor() = ( 01 00 00 00 )
.imagebase 0x10000000 .imagebase 0x10000000
.file alignment 0x00000200 .file alignment 0x00000200
.stackreserve 0x00100000 .stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI .subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY .corflags 0x00000001 // ILONLY
// Image base: 0x033B0000 // Image base: 0x00690000
// =============== CLASS MEMBERS DECLARATION =================== // =============== CLASS MEMBERS DECLARATION ===================
@ -40,6 +40,64 @@
.field private static class ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest field2 .field private static class ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest field2
.field private int32[] field3 .field private int32[] field3
.field private int16 field4 .field private int16 field4
.field private int32 '<InstanceProperty>k__BackingField'
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
.field private static int32 '<StaticProperty>k__BackingField'
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
.method public hidebysig specialname instance int32
get_InstanceProperty() cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
// Code size 11 (0xb)
.maxstack 1
.locals init (int32 V_0)
IL_0000: ldarg.0
IL_0001: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::'<InstanceProperty>k__BackingField'
IL_0006: stloc.0
IL_0007: br.s IL_0009
IL_0009: ldloc.0
IL_000a: ret
} // end of method InlineAssignmentTest::get_InstanceProperty
.method public hidebysig specialname instance void
set_InstanceProperty(int32 'value') cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::'<InstanceProperty>k__BackingField'
IL_0007: ret
} // end of method InlineAssignmentTest::set_InstanceProperty
.method public hidebysig specialname static
int32 get_StaticProperty() cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
// Code size 10 (0xa)
.maxstack 1
.locals init (int32 V_0)
IL_0000: ldsfld int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::'<StaticProperty>k__BackingField'
IL_0005: stloc.0
IL_0006: br.s IL_0008
IL_0008: ldloc.0
IL_0009: ret
} // end of method InlineAssignmentTest::get_StaticProperty
.method public hidebysig specialname static
void set_StaticProperty(int32 'value') cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: stsfld int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::'<StaticProperty>k__BackingField'
IL_0006: ret
} // end of method InlineAssignmentTest::set_StaticProperty
.method public hidebysig instance void .method public hidebysig instance void
SimpleInlineWithLocals() cil managed SimpleInlineWithLocals() cil managed
{ {
@ -378,6 +436,48 @@
IL_0021: ret IL_0021: ret
} // end of method InlineAssignmentTest::ArrayUsageWithMethods } // end of method InlineAssignmentTest::ArrayUsageWithMethods
.method public hidebysig instance int32
StaticPropertyTest() cil managed
{
// Code size 19 (0x13)
.maxstack 2
.locals init (int32 V_0)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: call instance int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::GetIndex()
IL_0007: dup
IL_0008: call void ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::set_StaticProperty(int32)
IL_000d: nop
IL_000e: stloc.0
IL_000f: br.s IL_0011
IL_0011: ldloc.0
IL_0012: ret
} // end of method InlineAssignmentTest::StaticPropertyTest
.method public hidebysig instance int32
InstancePropertyTest() cil managed
{
// Code size 22 (0x16)
.maxstack 3
.locals init (int32 V_0,
int32 V_1)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldarg.0
IL_0003: call instance int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::GetIndex()
IL_0008: dup
IL_0009: stloc.1
IL_000a: call instance void ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::set_InstanceProperty(int32)
IL_000f: nop
IL_0010: ldloc.1
IL_0011: stloc.0
IL_0012: br.s IL_0014
IL_0014: ldloc.0
IL_0015: ret
} // end of method InlineAssignmentTest::InstancePropertyTest
.method public hidebysig specialname rtspecialname .method public hidebysig specialname rtspecialname
instance void .ctor() cil managed instance void .ctor() cil managed
{ {
@ -388,6 +488,16 @@
IL_0006: ret IL_0006: ret
} // end of method InlineAssignmentTest::.ctor } // end of method InlineAssignmentTest::.ctor
.property instance int32 InstanceProperty()
{
.get instance int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::get_InstanceProperty()
.set instance void ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::set_InstanceProperty(int32)
} // end of property InlineAssignmentTest::InstanceProperty
.property int32 StaticProperty()
{
.get int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::get_StaticProperty()
.set void ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::set_StaticProperty(int32)
} // end of property InlineAssignmentTest::StaticProperty
} // end of class ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest } // end of class ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest

94
ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineAssignmentTest.opt.il

@ -10,7 +10,7 @@
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 4:0:0:0 .ver 4:0:0:0
} }
.assembly f1ibwsev .assembly idzuatf2
{ {
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx
@ -20,15 +20,15 @@
.hash algorithm 0x00008004 .hash algorithm 0x00008004
.ver 0:0:0:0 .ver 0:0:0:0
} }
.module f1ibwsev.dll .module idzuatf2.dll
// MVID: {8DB4E6AA-A0C2-49F3-8E9C-A28CE30F145A} // MVID: {0E6B135E-0CD7-486D-88EB-95340860F028}
.custom instance void [mscorlib]System.Security.UnverifiableCodeAttribute::.ctor() = ( 01 00 00 00 ) .custom instance void [mscorlib]System.Security.UnverifiableCodeAttribute::.ctor() = ( 01 00 00 00 )
.imagebase 0x10000000 .imagebase 0x10000000
.file alignment 0x00000200 .file alignment 0x00000200
.stackreserve 0x00100000 .stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI .subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY .corflags 0x00000001 // ILONLY
// Image base: 0x01400000 // Image base: 0x02A70000
// =============== CLASS MEMBERS DECLARATION =================== // =============== CLASS MEMBERS DECLARATION ===================
@ -40,6 +40,54 @@
.field private static class ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest field2 .field private static class ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest field2
.field private int32[] field3 .field private int32[] field3
.field private int16 field4 .field private int16 field4
.field private int32 '<InstanceProperty>k__BackingField'
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
.field private static int32 '<StaticProperty>k__BackingField'
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
.method public hidebysig specialname instance int32
get_InstanceProperty() cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::'<InstanceProperty>k__BackingField'
IL_0006: ret
} // end of method InlineAssignmentTest::get_InstanceProperty
.method public hidebysig specialname instance void
set_InstanceProperty(int32 'value') cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::'<InstanceProperty>k__BackingField'
IL_0007: ret
} // end of method InlineAssignmentTest::set_InstanceProperty
.method public hidebysig specialname static
int32 get_StaticProperty() cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
// Code size 6 (0x6)
.maxstack 8
IL_0000: ldsfld int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::'<StaticProperty>k__BackingField'
IL_0005: ret
} // end of method InlineAssignmentTest::get_StaticProperty
.method public hidebysig specialname static
void set_StaticProperty(int32 'value') cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: stsfld int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::'<StaticProperty>k__BackingField'
IL_0006: ret
} // end of method InlineAssignmentTest::set_StaticProperty
.method public hidebysig instance void .method public hidebysig instance void
SimpleInlineWithLocals() cil managed SimpleInlineWithLocals() cil managed
{ {
@ -309,6 +357,34 @@
IL_001c: ret IL_001c: ret
} // end of method InlineAssignmentTest::ArrayUsageWithMethods } // end of method InlineAssignmentTest::ArrayUsageWithMethods
.method public hidebysig instance int32
StaticPropertyTest() cil managed
{
// Code size 13 (0xd)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::GetIndex()
IL_0006: dup
IL_0007: call void ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::set_StaticProperty(int32)
IL_000c: ret
} // end of method InlineAssignmentTest::StaticPropertyTest
.method public hidebysig instance int32
InstancePropertyTest() cil managed
{
// Code size 16 (0x10)
.maxstack 3
.locals init (int32 V_0)
IL_0000: ldarg.0
IL_0001: ldarg.0
IL_0002: call instance int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::GetIndex()
IL_0007: dup
IL_0008: stloc.0
IL_0009: call instance void ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::set_InstanceProperty(int32)
IL_000e: ldloc.0
IL_000f: ret
} // end of method InlineAssignmentTest::InstancePropertyTest
.method public hidebysig specialname rtspecialname .method public hidebysig specialname rtspecialname
instance void .ctor() cil managed instance void .ctor() cil managed
{ {
@ -319,6 +395,16 @@
IL_0006: ret IL_0006: ret
} // end of method InlineAssignmentTest::.ctor } // end of method InlineAssignmentTest::.ctor
.property instance int32 InstanceProperty()
{
.get instance int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::get_InstanceProperty()
.set instance void ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::set_InstanceProperty(int32)
} // end of property InlineAssignmentTest::InstanceProperty
.property int32 StaticProperty()
{
.get int32 ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::get_StaticProperty()
.set void ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest::set_StaticProperty(int32)
} // end of property InlineAssignmentTest::StaticProperty
} // end of class ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest } // end of class ICSharpCode.Decompiler.Tests.TestCases.Pretty.InlineAssignmentTest

7
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -109,9 +109,6 @@ namespace ICSharpCode.Decompiler.CSharp
// CachedDelegateInitialization must run after ConditionDetection and before/in LoopingBlockTransform // CachedDelegateInitialization must run after ConditionDetection and before/in LoopingBlockTransform
// and must run before NullCoalescingTransform // and must run before NullCoalescingTransform
new CachedDelegateInitialization(), new CachedDelegateInitialization(),
new ILInlining(),
new TransformAssignment(), // must run before CopyPropagation
new CopyPropagation(),
new StatementTransform( new StatementTransform(
// per-block transforms that depend on each other, and thus need to // per-block transforms that depend on each other, and thus need to
// run interleaved (statement by statement). // run interleaved (statement by statement).
@ -121,11 +118,13 @@ namespace ICSharpCode.Decompiler.CSharp
// Inlining must be first, because it doesn't trigger re-runs. // Inlining must be first, because it doesn't trigger re-runs.
// Any other transform that opens up new inlining opportunities should call RequestRerun(). // Any other transform that opens up new inlining opportunities should call RequestRerun().
new ExpressionTransforms(), new ExpressionTransforms(),
new TransformAssignment(), // inline and compound assignments
new NullCoalescingTransform(), new NullCoalescingTransform(),
new NullableLiftingStatementTransform(), new NullableLiftingStatementTransform(),
new TransformArrayInitializers(), new TransformArrayInitializers(),
new TransformCollectionAndObjectInitializers() new TransformCollectionAndObjectInitializers()
) ),
new CopyPropagation()
} }
}, },
new ProxyCallReplacer(), new ProxyCallReplacer(),

76
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -47,28 +47,32 @@ namespace ICSharpCode.Decompiler.CSharp
public TranslatedExpression Build(CallInstruction inst) public TranslatedExpression Build(CallInstruction inst)
{ {
IMethod method = inst.Method; if (inst is NewObj newobj && IL.Transforms.DelegateConstruction.IsDelegateConstruction(newobj, true)) {
return HandleDelegateConstruction(newobj);
}
return Build(inst.OpCode, inst.Method, inst.Arguments).WithILInstruction(inst);
}
public ExpressionWithResolveResult Build(OpCode callOpCode, IMethod method, IReadOnlyList<ILInstruction> callArguments)
{
// Used for Call, CallVirt and NewObj // Used for Call, CallVirt and NewObj
TranslatedExpression target; TranslatedExpression target;
if (inst.OpCode == OpCode.NewObj) { if (callOpCode == OpCode.NewObj) {
if (IL.Transforms.DelegateConstruction.IsDelegateConstruction((NewObj)inst, true)) {
return HandleDelegateConstruction(inst);
}
target = default(TranslatedExpression); // no target target = default(TranslatedExpression); // no target
} else { } else {
target = expressionBuilder.TranslateTarget(method, inst.Arguments.FirstOrDefault(), inst.OpCode == OpCode.Call); target = expressionBuilder.TranslateTarget(method, callArguments.FirstOrDefault(), callOpCode == OpCode.Call);
} }
int firstParamIndex = (method.IsStatic || inst.OpCode == OpCode.NewObj) ? 0 : 1; int firstParamIndex = (method.IsStatic || callOpCode == OpCode.NewObj) ? 0 : 1;
// Translate arguments to the expected parameter types // Translate arguments to the expected parameter types
var arguments = new List<TranslatedExpression>(method.Parameters.Count); var arguments = new List<TranslatedExpression>(method.Parameters.Count);
Debug.Assert(inst.Arguments.Count == firstParamIndex + method.Parameters.Count); Debug.Assert(callArguments.Count == firstParamIndex + method.Parameters.Count);
var expectedParameters = method.Parameters.ToList(); var expectedParameters = method.Parameters.ToList();
bool isExpandedForm = false; bool isExpandedForm = false;
for (int i = 0; i < method.Parameters.Count; i++) { for (int i = 0; i < method.Parameters.Count; i++) {
var parameter = expectedParameters[i]; var parameter = expectedParameters[i];
var arg = expressionBuilder.Translate(inst.Arguments[firstParamIndex + i]); var arg = expressionBuilder.Translate(callArguments[firstParamIndex + i]);
if (parameter.IsParams && i + 1 == method.Parameters.Count) { if (parameter.IsParams && i + 1 == method.Parameters.Count) {
// Parameter is marked params // Parameter is marked params
// If the argument is an array creation, inline all elements into the call and add missing default values. // If the argument is an array creation, inline all elements into the call and add missing default values.
@ -90,7 +94,7 @@ namespace ICSharpCode.Decompiler.CSharp
expandedArguments.Add(expressionBuilder.GetDefaultValueExpression(elementType).WithoutILInstruction()); expandedArguments.Add(expressionBuilder.GetDefaultValueExpression(elementType).WithoutILInstruction());
} }
} }
if (IsUnambiguousCall(inst, target, method, Array.Empty<IType>(), expandedArguments) == OverloadResolutionErrors.None) { if (IsUnambiguousCall(callOpCode, target, method, Array.Empty<IType>(), expandedArguments) == OverloadResolutionErrors.None) {
isExpandedForm = true; isExpandedForm = true;
expectedParameters = expandedParameters; expectedParameters = expandedParameters;
arguments = expandedArguments.SelectList(a => new TranslatedExpression(a.Expression.Detach())); arguments = expandedArguments.SelectList(a => new TranslatedExpression(a.Expression.Detach()));
@ -124,7 +128,7 @@ namespace ICSharpCode.Decompiler.CSharp
ResolveResult rr = new CSharpInvocationResolveResult(target.ResolveResult, method, argumentResolveResults, isExpandedForm: isExpandedForm); ResolveResult rr = new CSharpInvocationResolveResult(target.ResolveResult, method, argumentResolveResults, isExpandedForm: isExpandedForm);
if (inst.OpCode == OpCode.NewObj) { if (callOpCode == OpCode.NewObj) {
if (settings.AnonymousTypes && method.DeclaringType.IsAnonymousType()) { if (settings.AnonymousTypes && method.DeclaringType.IsAnonymousType()) {
var argumentExpressions = arguments.SelectArray(arg => arg.Expression); var argumentExpressions = arguments.SelectArray(arg => arg.Expression);
AnonymousTypeCreateExpression atce = new AnonymousTypeCreateExpression(); AnonymousTypeCreateExpression atce = new AnonymousTypeCreateExpression();
@ -140,11 +144,10 @@ namespace ICSharpCode.Decompiler.CSharp
} }
} }
return atce return atce
.WithILInstruction(inst)
.WithRR(rr); .WithRR(rr);
} else { } else {
if (IsUnambiguousCall(inst, target, method, Array.Empty<IType>(), arguments) != OverloadResolutionErrors.None) { if (IsUnambiguousCall(callOpCode, target, method, Array.Empty<IType>(), arguments) != OverloadResolutionErrors.None) {
for (int i = 0; i < arguments.Count; i++) { for (int i = 0; i < arguments.Count; i++) {
if (settings.AnonymousTypes && expectedParameters[i].Type.ContainsAnonymousType()) { if (settings.AnonymousTypes && expectedParameters[i].Type.ContainsAnonymousType()) {
if (arguments[i].Expression is LambdaExpression lambda) { if (arguments[i].Expression is LambdaExpression lambda) {
@ -155,20 +158,20 @@ namespace ICSharpCode.Decompiler.CSharp
} }
} }
} }
return new ObjectCreateExpression(expressionBuilder.ConvertType(inst.Method.DeclaringType), arguments.SelectArray(arg => arg.Expression)) return new ObjectCreateExpression(expressionBuilder.ConvertType(method.DeclaringType), arguments.SelectArray(arg => arg.Expression))
.WithILInstruction(inst).WithRR(rr); .WithRR(rr);
} }
} else { } else {
int allowedParamCount = (method.ReturnType.IsKnownType(KnownTypeCode.Void) ? 1 : 0); int allowedParamCount = (method.ReturnType.IsKnownType(KnownTypeCode.Void) ? 1 : 0);
if (method.IsAccessor && (method.AccessorOwner.SymbolKind == SymbolKind.Indexer || expectedParameters.Count == allowedParamCount)) { if (method.IsAccessor && (method.AccessorOwner.SymbolKind == SymbolKind.Indexer || expectedParameters.Count == allowedParamCount)) {
return HandleAccessorCall(inst, target, method, arguments.ToList()); return HandleAccessorCall(callOpCode == OpCode.CallVirt, target, method, arguments.ToList());
} else if (method.Name == "Invoke" && method.DeclaringType.Kind == TypeKind.Delegate) { } else if (method.Name == "Invoke" && method.DeclaringType.Kind == TypeKind.Delegate) {
return new InvocationExpression(target, arguments.Select(arg => arg.Expression)).WithILInstruction(inst).WithRR(rr); return new InvocationExpression(target, arguments.Select(arg => arg.Expression)).WithRR(rr);
} else if (IsDelegateEqualityComparison(method, arguments)) { } else if (IsDelegateEqualityComparison(method, arguments)) {
return HandleDelegateEqualityComparison(method, arguments) return HandleDelegateEqualityComparison(method, arguments)
.WithILInstruction(inst).WithRR(rr); .WithRR(rr);
} else if (method.IsOperator && method.Name == "op_Implicit" && arguments.Count == 1) { } else if (method.IsOperator && method.Name == "op_Implicit" && arguments.Count == 1) {
return HandleImplicitConversion(inst, arguments[0]); return HandleImplicitConversion(method, arguments[0]);
} else { } else {
bool requireTypeArguments = false; bool requireTypeArguments = false;
bool targetCasted = false; bool targetCasted = false;
@ -176,7 +179,7 @@ namespace ICSharpCode.Decompiler.CSharp
IType[] typeArguments = Array.Empty<IType>(); IType[] typeArguments = Array.Empty<IType>();
OverloadResolutionErrors errors; OverloadResolutionErrors errors;
while ((errors = IsUnambiguousCall(inst, target, method, typeArguments, arguments)) != OverloadResolutionErrors.None) { while ((errors = IsUnambiguousCall(callOpCode, target, method, typeArguments, arguments)) != OverloadResolutionErrors.None) {
switch (errors) { switch (errors) {
case OverloadResolutionErrors.TypeInferenceFailed: case OverloadResolutionErrors.TypeInferenceFailed:
case OverloadResolutionErrors.WrongNumberOfTypeArguments: case OverloadResolutionErrors.WrongNumberOfTypeArguments:
@ -213,7 +216,7 @@ namespace ICSharpCode.Decompiler.CSharp
Expression targetExpr = target.Expression; Expression targetExpr = target.Expression;
string methodName = method.Name; string methodName = method.Name;
// HACK : convert this.Dispose() to ((IDisposable)this).Dispose(), if Dispose is an explicitly implemented interface method. // HACK : convert this.Dispose() to ((IDisposable)this).Dispose(), if Dispose is an explicitly implemented interface method.
if (inst.Method.IsExplicitInterfaceImplementation && targetExpr is ThisReferenceExpression) { if (method.IsExplicitInterfaceImplementation && targetExpr is ThisReferenceExpression) {
targetExpr = new CastExpression(expressionBuilder.ConvertType(method.ImplementedInterfaceMembers[0].DeclaringType), targetExpr); targetExpr = new CastExpression(expressionBuilder.ConvertType(method.ImplementedInterfaceMembers[0].DeclaringType), targetExpr);
methodName = method.ImplementedInterfaceMembers[0].Name; methodName = method.ImplementedInterfaceMembers[0].Name;
} }
@ -221,7 +224,7 @@ namespace ICSharpCode.Decompiler.CSharp
if (requireTypeArguments && (!settings.AnonymousTypes || !method.TypeArguments.Any(a => a.ContainsAnonymousType()))) if (requireTypeArguments && (!settings.AnonymousTypes || !method.TypeArguments.Any(a => a.ContainsAnonymousType())))
mre.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType)); mre.TypeArguments.AddRange(method.TypeArguments.Select(expressionBuilder.ConvertType));
var argumentExpressions = arguments.Select(arg => arg.Expression); var argumentExpressions = arguments.Select(arg => arg.Expression);
return new InvocationExpression(mre, argumentExpressions).WithILInstruction(inst).WithRR(rr); return new InvocationExpression(mre, argumentExpressions).WithRR(rr);
} }
} }
} }
@ -271,28 +274,27 @@ namespace ICSharpCode.Decompiler.CSharp
); );
} }
private TranslatedExpression HandleImplicitConversion(CallInstruction call, TranslatedExpression argument) private ExpressionWithResolveResult HandleImplicitConversion(IMethod method, TranslatedExpression argument)
{ {
var conversions = CSharpConversions.Get(expressionBuilder.compilation); var conversions = CSharpConversions.Get(expressionBuilder.compilation);
IType targetType = call.Method.ReturnType; IType targetType = method.ReturnType;
var conv = conversions.ImplicitConversion(argument.Type, targetType); var conv = conversions.ImplicitConversion(argument.Type, targetType);
if (!(conv.IsUserDefined && conv.Method.Equals(call.Method))) { if (!(conv.IsUserDefined && conv.Method.Equals(method))) {
// implicit conversion to targetType isn't directly possible, so first insert a cast to the argument type // implicit conversion to targetType isn't directly possible, so first insert a cast to the argument type
argument = argument.ConvertTo(call.Method.Parameters[0].Type, expressionBuilder); argument = argument.ConvertTo(method.Parameters[0].Type, expressionBuilder);
conv = conversions.ImplicitConversion(argument.Type, targetType); conv = conversions.ImplicitConversion(argument.Type, targetType);
} }
return new CastExpression(expressionBuilder.ConvertType(targetType), argument.Expression) return new CastExpression(expressionBuilder.ConvertType(targetType), argument.Expression)
.WithILInstruction(call)
.WithRR(new ConversionResolveResult(targetType, argument.ResolveResult, conv)); .WithRR(new ConversionResolveResult(targetType, argument.ResolveResult, conv));
} }
OverloadResolutionErrors IsUnambiguousCall(ILInstruction inst, TranslatedExpression target, IMethod method, IType[] typeArguments, IList<TranslatedExpression> arguments) OverloadResolutionErrors IsUnambiguousCall(OpCode callOpCode, TranslatedExpression target, IMethod method, IType[] typeArguments, IList<TranslatedExpression> arguments)
{ {
var lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentAssembly); var lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentAssembly);
var or = new OverloadResolution(resolver.Compilation, arguments.SelectArray(a => a.ResolveResult), typeArguments: typeArguments); var or = new OverloadResolution(resolver.Compilation, arguments.SelectArray(a => a.ResolveResult), typeArguments: typeArguments);
if (inst is NewObj newObj) { if (callOpCode == OpCode.NewObj) {
foreach (IMethod ctor in newObj.Method.DeclaringType.GetConstructors()) { foreach (IMethod ctor in method.DeclaringType.GetConstructors()) {
if (lookup.IsAccessible(ctor, allowProtectedAccess: resolver.CurrentTypeDefinition == newObj.Method.DeclaringTypeDefinition)) { if (lookup.IsAccessible(ctor, allowProtectedAccess: resolver.CurrentTypeDefinition == method.DeclaringTypeDefinition)) {
or.AddCandidate(ctor); or.AddCandidate(ctor);
} }
} }
@ -304,7 +306,7 @@ namespace ICSharpCode.Decompiler.CSharp
} }
if (or.BestCandidateErrors != OverloadResolutionErrors.None) if (or.BestCandidateErrors != OverloadResolutionErrors.None)
return or.BestCandidateErrors; return or.BestCandidateErrors;
if (!IsAppropriateCallTarget(method, or.GetBestCandidateWithSubstitutedTypeArguments(), inst.OpCode == OpCode.CallVirt)) if (!IsAppropriateCallTarget(method, or.GetBestCandidateWithSubstitutedTypeArguments(), callOpCode == OpCode.CallVirt))
return OverloadResolutionErrors.AmbiguousMatch; return OverloadResolutionErrors.AmbiguousMatch;
return OverloadResolutionErrors.None; return OverloadResolutionErrors.None;
} }
@ -327,12 +329,12 @@ namespace ICSharpCode.Decompiler.CSharp
return true; return true;
} }
TranslatedExpression HandleAccessorCall(ILInstruction inst, TranslatedExpression target, IMethod method, IList<TranslatedExpression> arguments) ExpressionWithResolveResult HandleAccessorCall(bool isVirtCall, TranslatedExpression target, IMethod method, IList<TranslatedExpression> arguments)
{ {
var lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentAssembly); var lookup = new MemberLookup(resolver.CurrentTypeDefinition, resolver.CurrentTypeDefinition.ParentAssembly);
var result = lookup.Lookup(target.ResolveResult, method.AccessorOwner.Name, EmptyList<IType>.Instance, isInvocation: false); var result = lookup.Lookup(target.ResolveResult, method.AccessorOwner.Name, EmptyList<IType>.Instance, isInvocation: false);
if (result.IsError || (result is MemberResolveResult && !IsAppropriateCallTarget(method.AccessorOwner, ((MemberResolveResult)result).Member, inst.OpCode == OpCode.CallVirt))) if (result.IsError || (result is MemberResolveResult && !IsAppropriateCallTarget(method.AccessorOwner, ((MemberResolveResult)result).Member, isVirtCall)))
target = target.ConvertTo(method.AccessorOwner.DeclaringType, expressionBuilder); target = target.ConvertTo(method.AccessorOwner.DeclaringType, expressionBuilder);
var rr = new MemberResolveResult(target.ResolveResult, method.AccessorOwner); var rr = new MemberResolveResult(target.ResolveResult, method.AccessorOwner);
@ -356,12 +358,12 @@ namespace ICSharpCode.Decompiler.CSharp
op = AssignmentOperatorType.Subtract; op = AssignmentOperatorType.Subtract;
} }
} }
return new AssignmentExpression(expr, op, value.Expression).WithILInstruction(inst).WithRR(new TypeResolveResult(method.AccessorOwner.ReturnType)); return new AssignmentExpression(expr, op, value.Expression).WithRR(new TypeResolveResult(method.AccessorOwner.ReturnType));
} else { } else {
if (arguments.Count == 0) if (arguments.Count == 0)
return new MemberReferenceExpression(target.Expression, method.AccessorOwner.Name).WithILInstruction(inst).WithRR(rr); return new MemberReferenceExpression(target.Expression, method.AccessorOwner.Name).WithRR(rr);
else else
return new IndexerExpression(target.Expression, arguments.Select(a => a.Expression)).WithILInstruction(inst).WithRR(rr); return new IndexerExpression(target.Expression, arguments.Select(a => a.Expression)).WithRR(rr);
} }
} }

15
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -1694,11 +1694,26 @@ namespace ICSharpCode.Decompiler.CSharp
return TranslateObjectAndCollectionInitializer(block); return TranslateObjectAndCollectionInitializer(block);
case BlockType.PostfixOperator: case BlockType.PostfixOperator:
return TranslatePostfixOperator(block); return TranslatePostfixOperator(block);
case BlockType.CallInlineAssign:
return TranslateSetterCallAssignment(block);
default: default:
return ErrorExpression("Unknown block type: " + block.Type); return ErrorExpression("Unknown block type: " + block.Type);
} }
} }
private TranslatedExpression TranslateSetterCallAssignment(Block block)
{
if (!block.MatchInlineAssignBlock(out var call, out var value)) {
// should never happen unless the ILAst is invalid
return ErrorExpression("Error: MatchInlineAssignBlock() returned false");
}
var arguments = call.Arguments.ToList();
arguments[arguments.Count - 1] = value;
return new CallBuilder(this, typeSystem, settings)
.Build(call.OpCode, call.Method, arguments)
.WithILInstruction(call);
}
TranslatedExpression TranslateObjectAndCollectionInitializer(Block block) TranslatedExpression TranslateObjectAndCollectionInitializer(Block block)
{ {
var stloc = block.Instructions.FirstOrDefault() as StLoc; var stloc = block.Instructions.FirstOrDefault() as StLoc;

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

@ -108,8 +108,13 @@ namespace ICSharpCode.Decompiler.IL
// only the last instruction may have an unreachable endpoint // only the last instruction may have an unreachable endpoint
Debug.Assert(!Instructions[i].HasFlag(InstructionFlags.EndPointUnreachable)); Debug.Assert(!Instructions[i].HasFlag(InstructionFlags.EndPointUnreachable));
} }
if (this.Type == BlockType.ControlFlow) { switch (this.Type) {
Debug.Assert(finalInstruction.OpCode == OpCode.Nop); case BlockType.ControlFlow:
Debug.Assert(finalInstruction.OpCode == OpCode.Nop);
break;
case BlockType.CallInlineAssign:
Debug.Assert(MatchInlineAssignBlock(out _, out _));
break;
} }
} }
@ -251,13 +256,62 @@ namespace ICSharpCode.Decompiler.IL
} }
return inst; return inst;
} }
public bool MatchInlineAssignBlock(out CallInstruction call, out ILInstruction value)
{
call = null;
value = null;
if (this.Type != BlockType.CallInlineAssign)
return false;
if (this.Instructions.Count != 1)
return false;
call = this.Instructions[0] as CallInstruction;
if (call == null || call.Arguments.Count == 0)
return false;
if (!call.Arguments.Last().MatchStLoc(out var tmp, out value))
return false;
if (!(tmp.IsSingleDefinition && tmp.LoadCount == 1))
return false;
return this.FinalInstruction.MatchLdLoc(tmp);
}
} }
public enum BlockType { public enum BlockType
{
/// <summary>
/// Block is used for control flow.
/// All blocks in block containers must have this type.
/// Control flow blocks cannot evaluate to a value (FinalInstruction must be Nop).
/// </summary>
ControlFlow, ControlFlow,
/// <summary>
/// Block is used for array initializers, e.g. `new int[] { expr1, expr2 }`.
/// </summary>
ArrayInitializer, ArrayInitializer,
CollectionInitializer, CollectionInitializer,
ObjectInitializer, ObjectInitializer,
PostfixOperator /// <summary>
/// Block is used for postfix operator on local variable.
/// </summary>
/// <remarks>
/// Postfix operators on non-locals use CompoundAssignmentInstruction with CompoundAssignmentType.EvaluatesToOldValue.
/// </remarks>
PostfixOperator,
/// <summary>
/// Block is used for using the result of a property setter inline.
/// Example: <code>Use(this.Property = value);</code>
/// This is only for inline assignments to property or indexers; other inline assignments work
/// by using the result value of the stloc/stobj instructions.
///
/// Constructed by TransformAssignment.
/// Can be deconstructed using Block.MatchInlineAssignBlock().
/// </summary>
/// <example>
/// Block {
/// call setter(..., stloc s(...))
/// final: ldloc s
/// }
/// </example>
CallInlineAssign
} }
} }

2
ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs

@ -107,7 +107,7 @@ namespace ICSharpCode.Decompiler.IL
internal static bool IsValidCompoundAssignmentTarget(ILInstruction inst) internal static bool IsValidCompoundAssignmentTarget(ILInstruction inst)
{ {
switch (inst.OpCode) { switch (inst.OpCode) {
case OpCode.LdLoc: // case OpCode.LdLoc: -- not valid -- does not mark the variable as written to
case OpCode.LdObj: case OpCode.LdObj:
return true; return true;
case OpCode.Call: case OpCode.Call:

250
ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs

@ -21,43 +21,49 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation; using ICSharpCode.Decompiler.TypeSystem.Implementation;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.IL.Transforms namespace ICSharpCode.Decompiler.IL.Transforms
{ {
/// <summary> /// <summary>
/// Constructs compound assignments and inline assignments. /// Constructs compound assignments and inline assignments.
/// </summary> /// </summary>
public class TransformAssignment : IBlockTransform public class TransformAssignment : IStatementTransform
{ {
BlockTransformContext context; StatementTransformContext context;
void IBlockTransform.Run(Block block, BlockTransformContext context) void IStatementTransform.Run(Block block, int pos, StatementTransformContext context)
{ {
this.context = context; this.context = context;
for (int i = block.Instructions.Count - 1; i >= 0; i--) { /*if (TransformPostIncDecOperatorOnAddress(block, i) || TransformPostIncDecOnStaticField(block, i) || TransformCSharp4PostIncDecOperatorOnAddress(block, i)) {
if (TransformPostIncDecOperatorOnAddress(block, i) || TransformPostIncDecOnStaticField(block, i) || TransformCSharp4PostIncDecOperatorOnAddress(block, i)) { block.Instructions.RemoveAt(i);
block.Instructions.RemoveAt(i); continue;
continue; }
} if (TransformPostIncDecOperator(block, i)) {
if (TransformPostIncDecOperator(block, i)) { block.Instructions.RemoveAt(i);
block.Instructions.RemoveAt(i); continue;
continue; }*/
} if (TransformInlineAssignmentStObjOrCall(block, pos) || TransformInlineAssignmentLocal(block, pos)) {
if (TransformInlineAssignmentStObj(block, i) || TransformInlineAssignmentLocal(block, i)) // both inline assignments create a top-level stloc which might affect inlining
continue; context.RequestRerun();
if (TransformInlineCompoundAssignmentCall(block, i)) return;
continue;
if (TransformRoslynCompoundAssignmentCall(block, i))
continue;
if (TransformRoslynPostIncDecOperatorOnAddress(block, i))
continue;
} }
/*
TransformInlineCompoundAssignmentCall(block, pos);
TransformRoslynCompoundAssignmentCall(block, pos);
// TODO: post-increment on local
// post-increment on address (e.g. field or array element)
TransformPostIncDecOperatorOnAddress(block, pos);
TransformRoslynPostIncDecOperatorOnAddress(block, pos);
// TODO: post-increment on call
*/
} }
/// <code> /// <code>
/// stloc s(value) /// stloc s(value)
/// stloc l(ldloc s) /// stloc l(ldloc s)
/// stobj(..., ldloc s) /// stobj(..., ldloc s)
/// where ... is pure and does not use s or l
/// --> /// -->
/// stloc l(stobj (..., value)) /// stloc l(stobj (..., value))
/// </code> /// </code>
@ -72,7 +78,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// stloc s(stobj (..., value)) /// stloc s(stobj (..., value))
/// </code> /// </code>
/// e.g. used for inline assignment to static field /// e.g. used for inline assignment to static field
bool TransformInlineAssignmentStObj(Block block, int pos) ///
/// -or-
///
/// <code>
/// stloc s(value)
/// call set_Property(..., ldloc s)
/// where the '...' arguments are free of side-effects and not using 's'
/// -->
/// stloc s(Block InlineAssign { call set_Property(..., stloc i(value)); final: ldloc i })
/// </code>
bool TransformInlineAssignmentStObjOrCall(Block block, int pos)
{ {
var inst = block.Instructions[pos] as StLoc; var inst = block.Instructions[pos] as StLoc;
// in some cases it can be a compiler-generated local // in some cases it can be a compiler-generated local
@ -96,22 +112,75 @@ namespace ICSharpCode.Decompiler.IL.Transforms
localStore = null; localStore = null;
nextPos = pos + 1; nextPos = pos + 1;
} }
if (!(block.Instructions[nextPos] is StObj stobj)) if (block.Instructions[nextPos] is StObj stobj) {
return false; if (!stobj.Value.MatchLdLoc(inst.Variable))
if (!stobj.Value.MatchLdLoc(inst.Variable) || inst.Variable.IsUsedWithin(stobj.Target)) return false;
return false; if (!SemanticHelper.IsPure(stobj.Target.Flags) || inst.Variable.IsUsedWithin(stobj.Target))
if (IsImplicitTruncation(inst.Value, stobj.Type)) { return false;
// 'stloc s' is implicitly truncating the value if (IsImplicitTruncation(inst.Value, stobj.Type)) {
// 'stobj' is implicitly truncating the value
return false;
}
context.Step("Inline assignment stobj", stobj);
block.Instructions.Remove(localStore);
block.Instructions.Remove(stobj);
stobj.Value = inst.Value;
inst.ReplaceWith(new StLoc(local, stobj));
return true;
} else if (block.Instructions[nextPos] is CallInstruction call) {
// call must be a setter call:
if (!(call.OpCode == OpCode.Call || call.OpCode == OpCode.CallVirt))
return false;
if (call.ResultType != StackType.Void || call.Arguments.Count == 0)
return false;
if (!call.Method.Equals((call.Method.AccessorOwner as IProperty)?.Setter))
return false;
if (!call.Arguments.Last().MatchLdLoc(inst.Variable))
return false;
foreach (var arg in call.Arguments.SkipLast(1)) {
if (!SemanticHelper.IsPure(arg.Flags) || inst.Variable.IsUsedWithin(arg))
return false;
}
if (IsImplicitTruncation(inst.Value, call.Method.Parameters.Last().Type)) {
// setter call is implicitly truncating the value
return false;
}
// stloc s(Block InlineAssign { call set_Property(..., stloc i(value)); final: ldloc i })
context.Step("Inline assignment call", call);
block.Instructions.Remove(localStore);
block.Instructions.Remove(call);
var newVar = context.Function.RegisterVariable(VariableKind.StackSlot, call.Method.Parameters.Last().Type);
call.Arguments[call.Arguments.Count - 1] = new StLoc(newVar, inst.Value);
inst.ReplaceWith(new StLoc(local, new Block(BlockType.CallInlineAssign) {
Instructions = { call },
FinalInstruction = new LdLoc(newVar)
}));
return true;
} else {
return false; return false;
} }
context.Step("Inline assignment stobj", stobj);
block.Instructions.Remove(localStore);
block.Instructions.Remove(stobj);
stobj.Value = inst.Value;
inst.ReplaceWith(new StLoc(local, stobj));
return true;
} }
static ILInstruction UnwrapSmallIntegerConv(ILInstruction inst, out Conv conv)
{
conv = inst as Conv;
if (conv != null && conv.Kind == ConversionKind.Truncate && conv.TargetType.IsSmallIntegerType()) {
// for compound assignments to small integers, the compiler emits a "conv" instruction
return conv.Argument;
} else {
return inst;
}
}
static bool ValidateCompoundAssign(BinaryNumericInstruction binary, Conv conv, IType targetType)
{
if (!CompoundAssignmentInstruction.IsBinaryCompatibleWithType(binary, targetType))
return false;
if (conv != null && !(conv.TargetType == targetType.ToPrimitiveType() && conv.CheckForOverflow == binary.CheckForOverflow))
return false; // conv does not match binary operation
return true;
}
/// <code> /// <code>
/// stloc s(binary(callvirt(getter), value)) /// stloc s(binary(callvirt(getter), value))
/// callvirt (setter, ldloc s) /// callvirt (setter, ldloc s)
@ -125,14 +194,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// in some cases it can be a compiler-generated local // in some cases it can be a compiler-generated local
if (mainStLoc == null || (mainStLoc.Variable.Kind != VariableKind.StackSlot && mainStLoc.Variable.Kind != VariableKind.Local)) if (mainStLoc == null || (mainStLoc.Variable.Kind != VariableKind.StackSlot && mainStLoc.Variable.Kind != VariableKind.Local))
return false; return false;
ILInstruction value = mainStLoc.Value; BinaryNumericInstruction binary = UnwrapSmallIntegerConv(mainStLoc.Value, out var conv) as BinaryNumericInstruction;
if (value is Conv conv && conv.Kind == ConversionKind.Truncate && conv.TargetType.IsSmallIntegerType()) {
// for compound assignments to small integers, the compiler emits a "conv" instruction
value = conv.Argument;
} else {
conv = null;
}
BinaryNumericInstruction binary = value as BinaryNumericInstruction;
ILVariable localVariable = mainStLoc.Variable; ILVariable localVariable = mainStLoc.Variable;
if (!localVariable.IsSingleDefinition) if (!localVariable.IsSingleDefinition)
return false; return false;
@ -151,10 +213,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (next.Descendants.Where(d => d.MatchLdLoc(localVariable)).Count() != 1) if (next.Descendants.Where(d => d.MatchLdLoc(localVariable)).Count() != 1)
return false; return false;
IType targetType = getterCall.Method.ReturnType; IType targetType = getterCall.Method.ReturnType;
if (!CompoundAssignmentInstruction.IsBinaryCompatibleWithType(binary, targetType)) if (!ValidateCompoundAssign(binary, conv, targetType))
return false; return false;
if (conv != null && !(conv.TargetType == targetType.ToPrimitiveType() && conv.CheckForOverflow == binary.CheckForOverflow))
return false; // conv does not match binary operation
context.Step($"Inline compound assignment to '{getterCall.Method.AccessorOwner.Name}'", setterCall); context.Step($"Inline compound assignment to '{getterCall.Method.AccessorOwner.Name}'", setterCall);
block.Instructions.RemoveAt(i + 1); // remove setter call block.Instructions.RemoveAt(i + 1); // remove setter call
mainStLoc.Value = new CompoundAssignmentInstruction( mainStLoc.Value = new CompoundAssignmentInstruction(
@ -267,14 +327,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// </remarks> /// </remarks>
internal static bool HandleStObjCompoundAssign(StObj inst, ILTransformContext context) internal static bool HandleStObjCompoundAssign(StObj inst, ILTransformContext context)
{ {
ILInstruction value = inst.Value; if (!(UnwrapSmallIntegerConv(inst.Value, out var conv) is BinaryNumericInstruction binary))
if (value is Conv conv && conv.Kind == ConversionKind.Truncate && conv.TargetType.IsSmallIntegerType()) {
// for compound assignments to small integers, the compiler emits a "conv" instruction
value = conv.Argument;
} else {
conv = null;
}
if (!(value is BinaryNumericInstruction binary))
return false; return false;
if (!(binary.Left is LdObj ldobj)) if (!(binary.Left is LdObj ldobj))
return false; return false;
@ -293,10 +346,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} else { } else {
targetType = ldobj.Type; targetType = ldobj.Type;
} }
if (!CompoundAssignmentInstruction.IsBinaryCompatibleWithType(binary, targetType)) if (!ValidateCompoundAssign(binary, conv, targetType))
return false; return false;
if (conv != null && !(conv.TargetType == targetType.ToPrimitiveType() && conv.CheckForOverflow == binary.CheckForOverflow))
return false; // conv does not match binary operation
context.Step("compound assignment", inst); context.Step("compound assignment", inst);
inst.ReplaceWith(new CompoundAssignmentInstruction( inst.ReplaceWith(new CompoundAssignmentInstruction(
binary, binary.Left, binary.Right, binary, binary.Left, binary.Right,
@ -310,16 +361,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// --> /// -->
/// stloc s(stloc l(value)) /// stloc s(stloc l(value))
/// </code> /// </code>
bool TransformInlineAssignmentLocal(Block block, int i) bool TransformInlineAssignmentLocal(Block block, int pos)
{ {
var inst = block.Instructions[i] as StLoc; var inst = block.Instructions[pos] as StLoc;
var nextInst = block.Instructions.ElementAtOrDefault(i + 1) as StLoc; var nextInst = block.Instructions.ElementAtOrDefault(pos + 1) as StLoc;
if (inst == null || nextInst == null) if (inst == null || nextInst == null)
return false; return false;
if (inst.Variable.Kind != VariableKind.StackSlot) if (inst.Variable.Kind != VariableKind.StackSlot)
return false; return false;
Debug.Assert(!inst.Variable.Type.IsSmallIntegerType()); Debug.Assert(!inst.Variable.Type.IsSmallIntegerType());
if (nextInst.Variable.Kind != VariableKind.Local || !nextInst.Value.MatchLdLoc(inst.Variable)) if (!(nextInst.Variable.Kind == VariableKind.Local || nextInst.Variable.Kind == VariableKind.Parameter))
return false;
if (!nextInst.Value.MatchLdLoc(inst.Variable))
return false; return false;
if (IsImplicitTruncation(inst.Value, nextInst.Variable.Type)) { if (IsImplicitTruncation(inst.Value, nextInst.Variable.Type)) {
// 'stloc l' is implicitly truncating the stack value // 'stloc l' is implicitly truncating the stack value
@ -329,7 +382,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var value = inst.Value; var value = inst.Value;
var var = nextInst.Variable; var var = nextInst.Variable;
var stackVar = inst.Variable; var stackVar = inst.Variable;
block.Instructions.RemoveAt(i); block.Instructions.RemoveAt(pos);
nextInst.ReplaceWith(new StLoc(stackVar, new StLoc(var, value))); nextInst.ReplaceWith(new StLoc(stackVar, new StLoc(var, value)));
return true; return true;
} }
@ -408,48 +461,49 @@ namespace ICSharpCode.Decompiler.IL.Transforms
nextInst.ReplaceWith(new StLoc(inst.Variable, assignment)); nextInst.ReplaceWith(new StLoc(inst.Variable, assignment));
return true; return true;
} }
/// ldaddress ::= ldelema | ldflda | ldsflda;
/// <code> /// <code>
/// stloc s(ldaddress) /// stobj(target, binary.add(stloc l(ldobj(target)), ldc.i4 1))
/// stloc l(ldobj(ldloc s)) /// where target is pure and does not use 'l'
/// stobj(ldloc s, binary.op(ldloc l, ldc.i4 1))
/// --> /// -->
/// stloc l(compound.op.old(ldobj(ldaddress), ldc.i4 1)) /// stloc l(compound.op.old(ldobj(target), ldc.i4 1))
/// </code> /// </code>
bool TransformPostIncDecOperatorOnAddress(Block block, int i) bool TransformPostIncDecOperatorOnAddress(Block block, int i)
{ {
var inst = block.Instructions[i] as StLoc; if (!(block.Instructions[i] is StObj stobj))
var nextInst = block.Instructions.ElementAtOrDefault(i + 1) as StLoc;
var stobj = block.Instructions.ElementAtOrDefault(i + 2) as StObj;
if (inst == null || nextInst == null || stobj == null)
return false; return false;
if (!inst.Variable.IsSingleDefinition || inst.Variable.LoadCount != 2) var binary = UnwrapSmallIntegerConv(stobj.Value, out var conv) as BinaryNumericInstruction;
if (binary == null || !binary.Right.MatchLdcI4(1))
return false; return false;
if (!(inst.Value is LdElema || inst.Value is LdFlda || inst.Value is LdsFlda)) if (!(binary.Operator == BinaryNumericOperator.Add || binary.Operator == BinaryNumericOperator.Sub))
return false; return false;
ILInstruction target; if (!(binary.Left is StLoc stloc))
IType targetType;
if (nextInst.Variable.Kind == VariableKind.StackSlot || !nextInst.Value.MatchLdObj(out target, out targetType) || !target.MatchLdLoc(inst.Variable))
return false; return false;
if (!stobj.Target.MatchLdLoc(inst.Variable)) if (!(stloc.Variable.Kind == VariableKind.Local || stloc.Variable.Kind == VariableKind.StackSlot))
return false; return false;
var binary = stobj.Value as BinaryNumericInstruction; if (!(stloc.Value is LdObj ldobj))
if (binary == null || !binary.Left.MatchLdLoc(nextInst.Variable) || !binary.Right.MatchLdcI4(1)
|| (binary.Operator != BinaryNumericOperator.Add && binary.Operator != BinaryNumericOperator.Sub))
return false; return false;
context.Step($"TransformPostIncDecOperator", inst); if (!SemanticHelper.IsPure(ldobj.Target.Flags))
var assignment = new CompoundAssignmentInstruction(binary, new LdObj(inst.Value, targetType), binary.Right, targetType, CompoundAssignmentType.EvaluatesToOldValue); return false;
stobj.ReplaceWith(new StLoc(nextInst.Variable, assignment)); if (!ldobj.Target.Match(stobj.Target).Success)
block.Instructions.RemoveAt(i + 1); return false;
if (stloc.Variable.IsUsedWithin(ldobj.Target))
return false;
IType targetType = ldobj.Type;
if (!ValidateCompoundAssign(binary, conv, targetType))
return false;
context.Step("TransformPostIncDecOperatorOnAddress", stobj);
block.Instructions[i] = new StLoc(stloc.Variable, new CompoundAssignmentInstruction(
binary, ldobj, binary.Right, targetType, CompoundAssignmentType.EvaluatesToOldValue));
return true; return true;
} }
/// <code> /// <code>
/// stloc l(ldobj(ldflda(target))) /// stloc l(ldobj(target))
/// stobj(ldflda(target), binary.op(ldloc l, ldc.i4 1)) /// stobj(target, binary.op(ldloc l, ldc.i4 1))
/// target is pure and does not use 'l'
/// --> /// -->
/// compound.op.old(ldobj(ldflda(target)), ldc.i4 1) /// stloc l(compound.op.old(ldobj(target), ldc.i4 1))
/// </code> /// </code>
bool TransformRoslynPostIncDecOperatorOnAddress(Block block, int i) bool TransformRoslynPostIncDecOperatorOnAddress(Block block, int i)
{ {
@ -457,21 +511,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var stobj = block.Instructions.ElementAtOrDefault(i + 1) as StObj; var stobj = block.Instructions.ElementAtOrDefault(i + 1) as StObj;
if (inst == null || stobj == null) if (inst == null || stobj == null)
return false; return false;
if (!inst.Variable.IsSingleDefinition || inst.Variable.LoadCount != 1) if (!(inst.Value is LdObj ldobj))
return false; return false;
if (!inst.Value.MatchLdObj(out var loadTarget, out var loadType) || !loadTarget.MatchLdFlda(out var fieldTarget, out var field)) if (!SemanticHelper.IsPure(ldobj.Target.Flags))
return false; return false;
if (!stobj.Target.MatchLdFlda(out var fieldTarget2, out var field2)) if (!ldobj.Target.Match(stobj.Target).Success)
return false; return false;
if (!fieldTarget.Match(fieldTarget2).Success || !field.Equals(field2)) if (inst.Variable.IsUsedWithin(ldobj.Target))
return false; return false;
var binary = stobj.Value as BinaryNumericInstruction; var binary = UnwrapSmallIntegerConv(stobj.Value, out var conv) as BinaryNumericInstruction;
if (binary == null || !binary.Left.MatchLdLoc(inst.Variable) || !binary.Right.MatchLdcI4(1) if (binary == null || !binary.Left.MatchLdLoc(inst.Variable) || !binary.Right.MatchLdcI4(1))
|| (binary.Operator != BinaryNumericOperator.Add && binary.Operator != BinaryNumericOperator.Sub)) return false;
if (!(binary.Operator == BinaryNumericOperator.Add || binary.Operator == BinaryNumericOperator.Sub))
return false; return false;
context.Step("TransformRoslynPostIncDecOperator", inst); var targetType = ldobj.Type;
stobj.ReplaceWith(new CompoundAssignmentInstruction(binary, inst.Value, binary.Right, loadType, CompoundAssignmentType.EvaluatesToOldValue)); if (!ValidateCompoundAssign(binary, conv, targetType))
block.Instructions.RemoveAt(i); return false;
context.Step("TransformRoslynPostIncDecOperatorOnAddress", inst);
inst.Value = new CompoundAssignmentInstruction(binary, inst.Value, binary.Right, targetType, CompoundAssignmentType.EvaluatesToOldValue);
block.Instructions.RemoveAt(i + 1);
return true; return true;
} }

Loading…
Cancel
Save