Browse Source

Add support for basic deconstruction conversions

pull/2119/head
Siegfried Pammer 6 years ago
parent
commit
9cb3fe3484
  1. 121
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/DeconstructionTests.cs
  2. 61
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs
  3. 15
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  4. 1
      ICSharpCode.Decompiler/IL/Instructions.cs
  5. 2
      ICSharpCode.Decompiler/IL/Instructions.tt
  6. 15
      ICSharpCode.Decompiler/IL/Instructions/DeconstructInstruction.cs
  7. 213
      ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs

121
ICSharpCode.Decompiler.Tests/TestCases/Correctness/DeconstructionTests.cs

@ -10,24 +10,48 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
{ {
public static void Main() public static void Main()
{ {
new CustomDeconstructionAndConversion().Test(); new DeconstructionTests().Test();
} }
private class CustomDeconstructionAndConversion
{
public struct MyInt public struct MyInt
{ {
public static implicit operator int(MyInt x) public static implicit operator int(MyInt x)
{ {
Console.WriteLine("int op_Implicit(MyInt)");
return 0; return 0;
} }
public static implicit operator MyInt(int x) public static implicit operator MyInt(int x)
{ {
Console.WriteLine("MyInt op_Implicit(int)");
return default(MyInt); return default(MyInt);
} }
} }
private class DeconstructionSource<T, T2>
{
public int Dummy {
get;
set;
}
public void Deconstruct(out T a, out T2 b)
{
Console.WriteLine("Deconstruct");
a = default(T);
b = default(T2);
}
}
private class AssignmentTargets
{
int id;
public AssignmentTargets(int id)
{
this.id = id;
}
public int IntField; public int IntField;
public int? NullableIntField; public int? NullableIntField;
@ -35,87 +59,73 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
public MyInt MyIntField; public MyInt MyIntField;
public MyInt? NullableMyIntField; public MyInt? NullableMyIntField;
private MyInt? nMy;
private MyInt my;
public int Int {
get;
set;
}
public int? NInt {
get;
set;
}
public MyInt My { public MyInt My {
get { get {
Console.WriteLine("get_My"); Console.WriteLine($"{id}.get_My()");
return my; return default;
} }
set { set {
Console.WriteLine("set_My"); Console.WriteLine($"{id}.set_My({value})");
my = value;
} }
} }
public MyInt? NMy { public MyInt? NMy {
get { get {
Console.WriteLine("get_NMy"); Console.WriteLine($"{id}.get_NMy()");
return nMy; return default;
} }
set { set {
Console.WriteLine("set_NMy"); Console.WriteLine($"{id}.set_NMy({value})");
nMy = value;
} }
} }
public static MyInt StaticMy { public int IntProperty {
get; get {
set; Console.WriteLine($"{id}.get_IntProperty()");
return default;
} }
set {
public static MyInt? StaticNMy { Console.WriteLine($"{id}.set_IntProperty({value})");
get;
set;
} }
public void Deconstruct(out MyInt? x, out MyInt y)
{
Console.WriteLine("Deconstruct(x, y)");
x = null;
y = default(MyInt);
} }
public CustomDeconstructionAndConversion GetValue() public uint UIntProperty {
{ get {
Console.WriteLine($"GetValue()"); Console.WriteLine($"{id}.get_UIntProperty()");
return new CustomDeconstructionAndConversion(); return default;
}
set {
Console.WriteLine($"{id}.set_UIntProperty({value})");
}
}
} }
public CustomDeconstructionAndConversion Get(int i) private DeconstructionSource<T, T2> GetSource<T, T2>()
{ {
Console.WriteLine($"Get({i})"); Console.WriteLine("GetSource()");
return new CustomDeconstructionAndConversion(); return new DeconstructionSource<T, T2>();
} }
private MyInt? GetNullableMyInt() private AssignmentTargets Get(int i)
{ {
throw new NotImplementedException(); Console.WriteLine($"Get({i})");
return new AssignmentTargets(i);
} }
public void Test() public void Test()
{ {
Property_NoDeconstruction_SwappedAssignments(); Property_NoDeconstruction_SwappedAssignments();
Property_NoDeconstruction_SwappedInits(); Property_NoDeconstruction_SwappedInits();
Property_IntToUIntConversion();
} }
public void Property_NoDeconstruction_SwappedAssignments() public void Property_NoDeconstruction_SwappedAssignments()
{ {
Console.WriteLine("Property_NoDeconstruction_SwappedAssignments:"); Console.WriteLine("Property_NoDeconstruction_SwappedAssignments:");
CustomDeconstructionAndConversion customDeconstructionAndConversion = Get(0); AssignmentTargets customDeconstructionAndConversion = Get(0);
CustomDeconstructionAndConversion customDeconstructionAndConversion2 = Get(1); AssignmentTargets customDeconstructionAndConversion2 = Get(1);
GetValue().Deconstruct(out MyInt? x, out MyInt y); GetSource<MyInt?, MyInt>().Deconstruct(out MyInt? x, out MyInt y);
MyInt myInt2 = customDeconstructionAndConversion2.My = y; MyInt myInt2 = customDeconstructionAndConversion2.My = y;
MyInt? myInt4 = customDeconstructionAndConversion.NMy = x; MyInt? myInt4 = customDeconstructionAndConversion.NMy = x;
} }
@ -123,9 +133,20 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
public void Property_NoDeconstruction_SwappedInits() public void Property_NoDeconstruction_SwappedInits()
{ {
Console.WriteLine("Property_NoDeconstruction_SwappedInits:"); Console.WriteLine("Property_NoDeconstruction_SwappedInits:");
CustomDeconstructionAndConversion customDeconstructionAndConversion = Get(1); AssignmentTargets customDeconstructionAndConversion = Get(1);
(Get(0).NMy, customDeconstructionAndConversion.My) = GetValue(); (Get(0).NMy, customDeconstructionAndConversion.My) = GetSource<MyInt?, MyInt>();
} }
public void Property_IntToUIntConversion()
{
Console.WriteLine("Property_IntToUIntConversion:");
AssignmentTargets t0 = Get(0);
AssignmentTargets t1 = Get(1);
int a;
uint b;
GetSource<int, uint>().Deconstruct(out a, out b);
t0.UIntProperty = (uint)a;
t1.IntProperty = (int)b;
} }
} }
} }

61
ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs

@ -22,8 +22,6 @@ using System.Runtime.InteropServices;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{ {
internal class DeconstructionTests internal class DeconstructionTests
{
private class CustomDeconstructionAndConversion
{ {
[StructLayout(LayoutKind.Sequential, Size = 1)] [StructLayout(LayoutKind.Sequential, Size = 1)]
public struct MyInt public struct MyInt
@ -39,6 +37,22 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
} }
} }
private class DeconstructionSource<T, T2>
{
public int Dummy {
get;
set;
}
public void Deconstruct(out T a, out T2 b)
{
a = default(T);
b = default(T2);
}
}
private class AssignmentTargets
{
public int IntField; public int IntField;
public int? NullableIntField; public int? NullableIntField;
@ -76,43 +90,51 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
get; get;
set; set;
} }
public void Deconstruct(out MyInt? x, out MyInt y)
{
x = null;
y = default(MyInt);
} }
public CustomDeconstructionAndConversion GetValue() private DeconstructionSource<T, T2> GetSource<T, T2>()
{ {
return null; return null;
} }
public CustomDeconstructionAndConversion Get(int i) private AssignmentTargets Get(int i)
{ {
return null; return null;
} }
private MyInt? GetNullableMyInt()
{
throw new NotImplementedException();
}
public void LocalVariable_NoConversion() public void LocalVariable_NoConversion()
{ {
MyInt? myInt3; MyInt? myInt3;
MyInt x; MyInt x;
(myInt3, x) = GetValue(); (myInt3, x) = GetSource<MyInt?, MyInt>();
Console.WriteLine(myInt3); Console.WriteLine(myInt3);
Console.WriteLine(x); Console.WriteLine(x);
} }
public void LocalVariable_NoConversion_ReferenceTypes()
{
string value;
string value2;
(value, value2) = GetSource<string, string>();
Console.WriteLine(value);
Console.WriteLine(value2);
}
public void LocalVariable_IntToLongConversion()
{
int value;
long value2;
(value, value2) = GetSource<int, int>();
Console.WriteLine(value);
Console.WriteLine(value2);
}
public void LocalVariable_NoConversion_ComplexValue() public void LocalVariable_NoConversion_ComplexValue()
{ {
MyInt? myInt3; MyInt? myInt3;
MyInt x; MyInt x;
(myInt3, x) = new CustomDeconstructionAndConversion { (myInt3, x) = new DeconstructionSource<MyInt?, MyInt> {
My = 3 Dummy = 3
}; };
Console.WriteLine(myInt3); Console.WriteLine(myInt3);
Console.WriteLine(x); Console.WriteLine(x);
@ -120,10 +142,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public void Property_NoConversion() public void Property_NoConversion()
{ {
(Get(0).NMy, Get(1).My) = GetValue(); (Get(0).NMy, Get(1).My) = GetSource<MyInt?, MyInt>();
}
} }
} }
} }

15
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -3392,6 +3392,16 @@ namespace ICSharpCode.Decompiler.CSharp
int assignmentPos = 0; int assignmentPos = 0;
var inits = inst.Init; var inits = inst.Init;
int initPos = 0; int initPos = 0;
Dictionary<ILVariable, ILVariable> conversionMapping = new Dictionary<ILVariable, ILVariable>();
foreach (var conv in inst.Conversions.Instructions) {
if (!DeconstructInstruction.IsConversionStLoc(conv, out var outputVariable, out var inputVariable))
continue;
conversionMapping.Add(inputVariable, outputVariable);
}
var lhs = ConstructTuple(inst.Pattern); var lhs = ConstructTuple(inst.Pattern);
return new AssignmentExpression(lhs, rhs) return new AssignmentExpression(lhs, rhs)
.WithILInstruction(inst) .WithILInstruction(inst)
@ -3403,7 +3413,10 @@ namespace ICSharpCode.Decompiler.CSharp
foreach (var subPattern in matchInstruction.SubPatterns.Cast<MatchInstruction>()) { foreach (var subPattern in matchInstruction.SubPatterns.Cast<MatchInstruction>()) {
if (subPattern.IsVar) { if (subPattern.IsVar) {
if (subPattern.HasDesignator) { if (subPattern.HasDesignator) {
expr.Elements.Add(ConstructAssignmentTarget(assignments[assignmentPos], subPattern.Variable)); if (!conversionMapping.TryGetValue(subPattern.Variable, out ILVariable value)) {
value = subPattern.Variable;
}
expr.Elements.Add(ConstructAssignmentTarget(assignments[assignmentPos], value));
assignmentPos++; assignmentPos++;
} else } else
expr.Elements.Add(new IdentifierExpression("_")); expr.Elements.Add(new IdentifierExpression("_"));

1
ICSharpCode.Decompiler/IL/Instructions.cs

@ -6226,7 +6226,6 @@ namespace ICSharpCode.Decompiler.IL
base.CheckInvariant(phase); base.CheckInvariant(phase);
Debug.Assert(phase <= ILPhase.InILReader || this.IsDescendantOf(variable.Function)); Debug.Assert(phase <= ILPhase.InILReader || this.IsDescendantOf(variable.Function));
Debug.Assert(phase <= ILPhase.InILReader || variable.Function.Variables[variable.IndexInFunction] == variable); Debug.Assert(phase <= ILPhase.InILReader || variable.Function.Variables[variable.IndexInFunction] == variable);
Debug.Assert(testedOperand.ResultType == StackType.O);
AdditionalInvariants(); AdditionalInvariants();
} }
} }

2
ICSharpCode.Decompiler/IL/Instructions.tt

@ -339,7 +339,7 @@
CustomClassName("MatchInstruction"), HasVariableOperand("Store"), HasMethodOperand, CustomClassName("MatchInstruction"), HasVariableOperand("Store"), HasMethodOperand,
BoolFlag("IsDeconstructCall"), BoolFlag("CheckType"), BoolFlag("CheckNotNull"), BoolFlag("IsDeconstructCall"), BoolFlag("CheckType"), BoolFlag("CheckNotNull"),
CustomChildren(new []{ CustomChildren(new []{
new ChildInfo("testedOperand") { CanInlineInto = true, ExpectedTypes = new[] { "O" } }, new ChildInfo("testedOperand") { CanInlineInto = true },
new ChildInfo("subPatterns") { IsCollection = true } new ChildInfo("subPatterns") { IsCollection = true }
}), ResultType("I4"), CustomWriteTo, SideEffect, MayThrow, ControlFlow, CustomInvariant("AdditionalInvariants();")), }), ResultType("I4"), CustomWriteTo, SideEffect, MayThrow, ControlFlow, CustomInvariant("AdditionalInvariants();")),

15
ICSharpCode.Decompiler/IL/Instructions/DeconstructInstruction.cs

@ -21,6 +21,8 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.IL namespace ICSharpCode.Decompiler.IL
{ {
partial class DeconstructInstruction partial class DeconstructInstruction
@ -193,17 +195,18 @@ namespace ICSharpCode.Decompiler.IL
case Conv conv: case Conv conv:
input = conv.Argument; input = conv.Argument;
break; break;
case Call { Method: { IsOperator: true, Name: "op_Implicit" }, Arguments: { Count: 1 } } call: //case Call { Method: { IsOperator: true, Name: "op_Implicit" }, Arguments: { Count: 1 } } call:
input = call.Arguments[0]; // input = call.Arguments[0];
break; // break;
default: default:
return false; return false;
} }
return input.MatchLdLoc(out inputVariable) || input.MatchLdLoca(out inputVariable); return input.MatchLdLoc(out inputVariable) || input.MatchLdLoca(out inputVariable);
} }
internal static bool IsAssignment(ILInstruction inst, out ILInstruction value) internal static bool IsAssignment(ILInstruction inst, out IType expectedType, out ILInstruction value)
{ {
expectedType = null;
value = null; value = null;
switch (inst) { switch (inst) {
case CallInstruction call: case CallInstruction call:
@ -218,9 +221,11 @@ namespace ICSharpCode.Decompiler.IL
return false; return false;
} }
} }
expectedType = call.Method.Parameters.Last().Type;
value = call.Arguments.Last(); value = call.Arguments.Last();
return true; return true;
case StLoc stloc: case StLoc stloc:
expectedType = stloc.Variable.Type;
value = stloc.Value; value = stloc.Value;
return true; return true;
case StObj stobj: case StObj stobj:
@ -255,7 +260,7 @@ namespace ICSharpCode.Decompiler.IL
Debug.Assert(this.conversions.FinalInstruction is Nop); Debug.Assert(this.conversions.FinalInstruction is Nop);
foreach (var inst in assignments.Instructions) { foreach (var inst in assignments.Instructions) {
if (!(IsAssignment(inst, out var value) && value.MatchLdLoc(out var inputVariable))) if (!(IsAssignment(inst, out _, out var value) && value.MatchLdLoc(out var inputVariable)))
throw new InvalidOperationException("inst is not an assignment!"); throw new InvalidOperationException("inst is not an assignment!");
Debug.Assert(patternVariables.Contains(inputVariable) || conversionVariables.Contains(inputVariable)); Debug.Assert(patternVariables.Contains(inputVariable) || conversionVariables.Contains(inputVariable));
} }

213
ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs

@ -20,6 +20,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.CSharp.Resolver;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util; using ICSharpCode.Decompiler.Util;
@ -31,7 +32,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
class DeconstructionTransform : IStatementTransform class DeconstructionTransform : IStatementTransform
{ {
StatementTransformContext context; StatementTransformContext context;
readonly Dictionary<ILVariable, int> deconstructionResultsLookup = new Dictionary<ILVariable, int>();
/* /*
stloc tuple(call MakeIntIntTuple(ldloc this)) stloc tuple(call MakeIntIntTuple(ldloc this))
@ -64,7 +64,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
try { try {
this.context = context; this.context = context;
this.deconstructionResultsLookup.Clear();
if (TransformDeconstruction(block, pos)) if (TransformDeconstruction(block, pos))
return; return;
@ -72,10 +71,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return; return;
} finally { } finally {
this.context = null; this.context = null;
this.deconstructionResultsLookup.Clear();
} }
} }
struct ConversionInfo
{
public IType inputType;
public Conv conv;
}
/// <summary> /// <summary>
/// stloc v(lhs) /// stloc v(lhs)
/// expr(..., deconstruct { ... }, ...) /// expr(..., deconstruct { ... }, ...)
@ -112,11 +116,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms
bool TransformDeconstruction(Block block, int pos) bool TransformDeconstruction(Block block, int pos)
{ {
int startPos = pos; int startPos = pos;
if (!MatchDeconstruction(block, ref pos, out var deconstructMethod, out var rootTestedOperand, out var deconstructionResults)) Action<DeconstructInstruction> delayedActions = null;
if (MatchDeconstruction(block.Instructions[pos], out var deconstructMethod,
out var rootTestedOperand, out var deconstructionResults,
out var deconstructionResultsLookup))
{
pos++;
}
else {
return false; return false;
if (!MatchConversion(block, ref pos)) }
if (!MatchConversions(block, ref pos, deconstructionResultsLookup, out var conversions, out var conversionStLocs))
return false; return false;
if (!MatchAssignments(block, ref pos, out var assignments))
if (!MatchAssignments(block, ref pos, deconstructionResultsLookup, conversions, conversionStLocs, ref delayedActions))
return false; return false;
context.Step("Deconstruction", block.Instructions[startPos]); context.Step("Deconstruction", block.Instructions[startPos]);
DeconstructInstruction replacement = new DeconstructInstruction(); DeconstructInstruction replacement = new DeconstructInstruction();
@ -133,24 +146,34 @@ namespace ICSharpCode.Decompiler.IL.Transforms
int index = 0; int index = 0;
foreach (var result in deconstructionResults) { foreach (var result in deconstructionResults) {
result.Kind = VariableKind.PatternLocal; result.Kind = VariableKind.PatternLocal;
replacement.Pattern.SubPatterns.Add(new MatchInstruction(result, new DeconstructResultInstruction(index, result.StackType, new LdLoc(rootTempVariable)))); replacement.Pattern.SubPatterns.Add(
new MatchInstruction(
result,
new DeconstructResultInstruction(index, result.StackType, new LdLoc(rootTempVariable))
)
);
index++; index++;
} }
replacement.Conversions = new Block(BlockKind.DeconstructionConversions); replacement.Conversions = new Block(BlockKind.DeconstructionConversions);
TransformAssignments(replacement, assignments); foreach (var convInst in conversionStLocs) {
replacement.Conversions.Instructions.Add(convInst);
}
replacement.Assignments = new Block(BlockKind.DeconstructionAssignments);
delayedActions?.Invoke(replacement);
block.Instructions[startPos] = replacement; block.Instructions[startPos] = replacement;
block.Instructions.RemoveRange(startPos + 1, pos - startPos - 1); block.Instructions.RemoveRange(startPos + 1, pos - startPos - 1);
return true; return true;
} }
bool MatchDeconstruction(Block block, ref int pos, out IMethod deconstructMethod, bool MatchDeconstruction(ILInstruction inst, out IMethod deconstructMethod,
out ILInstruction testedOperand, out List<ILVariable> deconstructionResults) out ILInstruction testedOperand, out List<ILVariable> deconstructionResults,
out Dictionary<ILVariable, int> deconstructionResultsLookup)
{ {
testedOperand = null; testedOperand = null;
deconstructMethod = null; deconstructMethod = null;
deconstructionResults = null; deconstructionResults = null;
// TODO nested deconstruction / tuple deconstruction deconstructionResultsLookup = null;
if (!(block.Instructions[pos] is CallInstruction call)) if (!(inst is CallInstruction call))
return false; return false;
if (!MatchInstruction.IsDeconstructMethod(call.Method)) if (!MatchInstruction.IsDeconstructMethod(call.Method))
return false; return false;
@ -159,6 +182,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (call.Arguments.Count < 3) if (call.Arguments.Count < 3)
return false; return false;
deconstructionResults = new List<ILVariable>(); deconstructionResults = new List<ILVariable>();
deconstructionResultsLookup = new Dictionary<ILVariable, int>();
for (int i = 1; i < call.Arguments.Count; i++) { for (int i = 1; i < call.Arguments.Count; i++) {
if (!call.Arguments[i].MatchLdLoca(out var v)) if (!call.Arguments[i].MatchLdLoca(out var v))
return false; return false;
@ -171,65 +195,172 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
testedOperand = call.Arguments[0]; testedOperand = call.Arguments[0];
deconstructMethod = call.Method; deconstructMethod = call.Method;
return true;
}
bool MatchConversions(Block block, ref int pos,
Dictionary<ILVariable, int> deconstructionResultsLookup,
out Dictionary<ILVariable, ConversionInfo> conversions,
out List<StLoc> conversionStLocs)
{
conversions = new Dictionary<ILVariable, ConversionInfo>();
conversionStLocs = new List<StLoc>();
int previousIndex = -1;
while (MatchConversion(
block.Instructions.ElementAtOrDefault(pos), out var inputInstruction,
out var outputVariable, out var info))
{
if (!inputInstruction.MatchLdLoc(out var inputVariable))
return false;
if (!deconstructionResultsLookup.TryGetValue(inputVariable, out int index))
return false;
if (index <= previousIndex)
return false;
if (!(outputVariable.IsSingleDefinition && outputVariable.LoadCount == 1))
return false;
deconstructionResultsLookup.Remove(inputVariable);
deconstructionResultsLookup.Add(outputVariable, index);
conversions.Add(outputVariable, info);
conversionStLocs.Add((StLoc)block.Instructions[pos]);
pos++; pos++;
previousIndex = index;
}
return true; return true;
} }
bool MatchConversion(Block block, ref int pos) bool MatchConversion(ILInstruction inst, out ILInstruction inputInstruction,
out ILVariable outputVariable, out ConversionInfo info)
{ {
// TODO info = default;
inputInstruction = null;
if (!inst.MatchStLoc(out outputVariable, out var value))
return false;
if (!(value is Conv conv))
return false;
info = new ConversionInfo {
inputType = conv.Argument.InferType(context.TypeSystem),
conv = conv
};
inputInstruction = conv.Argument;
return true; return true;
} }
bool MatchAssignments(Block block, ref int pos, out List<ILInstruction> assignments) bool MatchAssignments(Block block, ref int pos,
Dictionary<ILVariable, int> deconstructionResultsLookup,
Dictionary<ILVariable, ConversionInfo> conversions,
List<StLoc> conversionStLocs,
ref Action<DeconstructInstruction> delayedActions)
{ {
assignments = new List<ILInstruction>(); int previousIndex = -1;
int expectedIndex = 0; int conversionStLocIndex = 0;
while (MatchAssignment(block.Instructions.ElementAtOrDefault(pos), out var resultVariable)) { int startPos = pos;
while (MatchAssignment(block.Instructions.ElementAtOrDefault(pos), out var targetType, out var valueInst, out var addAssignment)) {
if (!valueInst.MatchLdLoc(out var resultVariable))
return false;
if (!deconstructionResultsLookup.TryGetValue(resultVariable, out int index)) if (!deconstructionResultsLookup.TryGetValue(resultVariable, out int index))
return false; return false;
if (index != expectedIndex) if (index <= previousIndex)
return false;
AddMissingAssignmentsForConversions(index, ref delayedActions);
if (!conversions.TryGetValue(resultVariable, out var conversionInfo)) {
conversionInfo = new ConversionInfo {
inputType = resultVariable.Type
};
}
if (block.Instructions[pos].MatchStLoc(out var assignmentTarget, out _)
&& assignmentTarget.Kind == VariableKind.StackSlot
&& assignmentTarget.IsSingleDefinition
&& conversionInfo.conv == null)
{
delayedActions += _ => {
assignmentTarget.Type = conversionInfo.inputType;
};
}
else
{
if (!IsCompatibleImplicitConversion(targetType, conversionInfo))
return false; return false;
assignments.Add(block.Instructions[pos]); }
delayedActions += addAssignment;
pos++; pos++;
expectedIndex++; previousIndex = index;
}
AddMissingAssignmentsForConversions(int.MaxValue, ref delayedActions);
return startPos != pos;
void AddMissingAssignmentsForConversions(int index, ref Action<DeconstructInstruction> delayedActions)
{
while (conversionStLocIndex < conversionStLocs.Count) {
var stLoc = conversionStLocs[conversionStLocIndex];
int conversionResultIndex = deconstructionResultsLookup[stLoc.Variable];
if (conversionResultIndex >= index)
break;
if (conversionResultIndex > previousIndex) {
delayedActions += (DeconstructInstruction deconstructInst) => {
var freshVar = context.Function.RegisterVariable(VariableKind.StackSlot, stLoc.Variable.Type);
deconstructInst.Assignments.Instructions.Add(new StLoc(stLoc.Variable, new LdLoc(freshVar)));
stLoc.Variable = freshVar;
};
}
conversionStLocIndex++;
}
} }
return assignments.Count > 0;
} }
bool MatchAssignment(ILInstruction inst, out ILVariable resultVariable) bool MatchAssignment(ILInstruction inst, out IType targetType, out ILInstruction valueInst, out Action<DeconstructInstruction> addAssignment)
{ {
resultVariable = null; addAssignment = null;
if (inst.MatchStLoc(out var v, out var value) if (inst.MatchStLoc(out var v, out var value)
&& value is Block block && block.MatchInlineAssignBlock(out var call, out var valueInst)) { && value is Block block && block.MatchInlineAssignBlock(out var call, out valueInst)) {
if (!DeconstructInstruction.IsAssignment(call, out _)) if (!DeconstructInstruction.IsAssignment(call, out targetType, out _))
return false; return false;
if (!(v.IsSingleDefinition && v.LoadCount == 0)) if (!(v.IsSingleDefinition && v.LoadCount == 0))
return false; return false;
} else if (DeconstructInstruction.IsAssignment(inst, out valueInst)) { var valueInstCopy = valueInst;
addAssignment = (DeconstructInstruction deconstructInst) => {
call.Arguments[call.Arguments.Count - 1] = valueInstCopy;
deconstructInst.Assignments.Instructions.Add(call);
};
return true;
} else if (DeconstructInstruction.IsAssignment(inst, out targetType, out valueInst)) {
// OK - use the assignment as is // OK - use the assignment as is
addAssignment = (DeconstructInstruction deconstructInst) => {
deconstructInst.Assignments.Instructions.Add(inst);
};
return true;
} else { } else {
return false; return false;
} }
if (!valueInst.MatchLdLoc(out resultVariable))
return false;
return true;
} }
void TransformAssignments(DeconstructInstruction replacement, List<ILInstruction> assignments) bool IsCompatibleImplicitConversion(IType targetType, ConversionInfo conversionInfo)
{ {
replacement.Assignments = new Block(BlockKind.DeconstructionAssignments); var c = CSharpConversions.Get(context.TypeSystem)
foreach (var assignment in assignments) { .ImplicitConversion(conversionInfo.inputType, targetType);
var transformed = assignment; if (!c.IsValid)
if (transformed.MatchStLoc(out _, out var value) return false;
&& value is Block block var inputType = conversionInfo.inputType;
&& block.MatchInlineAssignBlock(out var call, out value)) { var conv = conversionInfo.conv;
call.Arguments[call.Arguments.Count - 1] = value; if (c.IsIdentityConversion) {
transformed = call; return conv == null || conv.Kind == ConversionKind.Nop;
}
if (c.IsNumericConversion) {
switch (conv.Kind) {
case ConversionKind.IntToFloat:
return inputType.GetSign() == conv.InputSign;
case ConversionKind.FloatPrecisionChange:
return true;
case ConversionKind.SignExtend:
return inputType.GetSign() == Sign.Signed;
case ConversionKind.ZeroExtend:
return inputType.GetSign() == Sign.Unsigned;
default:
return false;
} }
replacement.Assignments.Instructions.Add(transformed);
} }
return false;
} }
} }
} }

Loading…
Cancel
Save