Browse Source

Merge pull request #3041 from icsharpcode/roslyn46

Roslyn 4.6
pull/3045/head
Christoph Wille 2 years ago committed by GitHub
parent
commit
397661b2ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 37
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/ValueTypeCall.cs
  2. 1
      ICSharpCode.Decompiler/DecompilerSettings.cs
  3. 17
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  4. 336
      ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs
  5. 6
      packages.props

37
ICSharpCode.Decompiler.Tests/TestCases/Correctness/ValueTypeCall.cs

@ -16,6 +16,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -16,6 +16,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
public void Dispose()
{
Console.WriteLine("MutValueType disposed on {0}", val);
val = val + 1;
}
public override string ToString()
@ -67,6 +68,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -67,6 +68,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
gvt.Call(ref gvt);
new ValueTypeCall().InstanceFieldTests();
ForEach();
#if CS73
DisposeMultipleTimes(ref m, in m);
ToStringGeneric(ref m, in m);
#endif
}
static void RefParameter(ref MutValueType m)
@ -213,5 +218,37 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -213,5 +218,37 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
}
Console.WriteLine("after: " + list[0].val);
}
#if CS73
static void DisposeMultipleTimes<T>(ref T mutRef, in T immutableRef) where T : struct, IDisposable
{
Console.WriteLine("DisposeMultipleTimes:");
mutRef.Dispose();
mutRef.Dispose();
T copyFromMut = mutRef;
copyFromMut.Dispose();
immutableRef.Dispose();
immutableRef.Dispose();
T copyFromImmutable = immutableRef;
copyFromImmutable.Dispose();
mutRef.Dispose();
immutableRef.Dispose();
}
static void ToStringGeneric<T>(ref T mutRef, in T immutableRef) where T : struct
{
Console.WriteLine("ToStringGeneric:");
Console.WriteLine(mutRef.ToString());
Console.WriteLine(mutRef.ToString());
T copyFromMut = mutRef;
Console.WriteLine(copyFromMut.ToString());
Console.WriteLine(immutableRef.ToString());
Console.WriteLine(immutableRef.ToString());
T copyFromImmutable = immutableRef;
Console.WriteLine(copyFromImmutable.ToString());
Console.WriteLine(mutRef.ToString());
Console.WriteLine(immutableRef.ToString());
}
#endif
}
}

1
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -379,6 +379,7 @@ namespace ICSharpCode.Decompiler @@ -379,6 +379,7 @@ namespace ICSharpCode.Decompiler
}
[Obsolete("Renamed to ScopedRef. This property will be removed in a future version of the decompiler.")]
[Browsable(false)]
public bool LifetimeAnnotations {
get { return ScopedRef; }
set { ScopedRef = value; }

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

@ -301,7 +301,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -301,7 +301,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// Thus, we have to ensure we're operating on an r-value.
// Additionally, we cannot inline in cases where the C# compiler prohibits the direct use
// of the rvalue (e.g. M(ref (MyStruct)obj); is invalid).
if (IsUsedAsThisPointerInCall(loadInst, out var method))
if (IsUsedAsThisPointerInCall(loadInst, out var method, out var constrainedTo))
{
if (options.HasFlag(InliningOptions.Aggressive))
{
@ -321,7 +321,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -321,7 +321,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
case ExpressionClassification.ReadonlyLValue:
// For struct method calls on readonly lvalues, the C# compiler
// only generates a temporary if it isn't a "readonly struct"
return MethodRequiresCopyForReadonlyLValue(method);
return MethodRequiresCopyForReadonlyLValue(method, constrainedTo);
default:
throw new InvalidOperationException("invalid expression classification");
}
@ -337,11 +337,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -337,11 +337,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
internal static bool MethodRequiresCopyForReadonlyLValue(IMethod method)
internal static bool MethodRequiresCopyForReadonlyLValue(IMethod method, IType constrainedTo = null)
{
if (method == null)
return true;
var type = method.DeclaringType;
var type = constrainedTo ?? method.DeclaringType;
if (type.IsReferenceType == true)
return false; // reference types are never implicitly copied
if (method.ThisIsRefReadOnly)
@ -351,12 +351,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -351,12 +351,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
internal static bool IsUsedAsThisPointerInCall(LdLoca ldloca)
{
return IsUsedAsThisPointerInCall(ldloca, out _);
return IsUsedAsThisPointerInCall(ldloca, out _, out _);
}
static bool IsUsedAsThisPointerInCall(LdLoca ldloca, out IMethod method)
static bool IsUsedAsThisPointerInCall(LdLoca ldloca, out IMethod method, out IType constrainedType)
{
method = null;
constrainedType = null;
if (ldloca.Variable.Type.IsReferenceType ?? false)
return false;
ILInstruction inst = ldloca;
@ -370,7 +371,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -370,7 +371,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
case OpCode.Call:
case OpCode.CallVirt:
method = ((CallInstruction)inst.Parent).Method;
var callInst = (CallInstruction)inst.Parent;
method = callInst.Method;
constrainedType = callInst.ConstrainedTo;
if (method.IsAccessor)
{
if (method.AccessorKind == MethodSemanticsAttributes.Getter)

336
ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs

@ -78,6 +78,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -78,6 +78,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
changed = true;
continue;
}
if (MatchRoslynSwitchOnStringUsingLengthAndChar(block.Instructions, ref i))
{
changed = true;
continue;
}
}
if (!changed)
continue;
@ -1009,7 +1014,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1009,7 +1014,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
var stringValues = new List<(string Value, ILInstruction TargetBlockOrLeave)>();
SwitchSection defaultSection = switchInst.Sections.MaxBy(s => s.Labels.Count());
SwitchSection defaultSection = switchInst.GetDefaultSection();
if (!(defaultSection.Body.MatchBranch(out Block exitOrDefaultBlock) || defaultSection.Body.MatchLeave(out _)))
return false;
foreach (var section in switchInst.Sections)
@ -1140,6 +1145,335 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1140,6 +1145,335 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
private bool MatchRoslynSwitchOnStringUsingLengthAndChar(InstructionCollection<ILInstruction> instructions, ref int i)
{
// implements https://github.com/dotnet/roslyn/pull/66081
if (i >= instructions.Count - 1)
return false;
// if (comp(ldloc switchValueVar == ldnull)) br nullCase
// br nextBlock
if (!instructions[i].MatchIfInstruction(out var condition, out var exitBlockJump)
|| !condition.MatchCompEqualsNull(out var ldloc)
|| ldloc is not LdLoc { Variable: var switchValueVar })
return false;
if (!instructions[i + 1].MatchBranch(out var defaultCase))
return false;
if (!exitBlockJump.MatchBranch(out var nullCase))
return false;
// if (comp(ldloc switchValueVar == ldnull)) br defaultCase
// br switchOnLengthBlock
Block switchOnLengthBlock;
if (defaultCase.IncomingEdgeCount == 1
&& defaultCase.Instructions[0].MatchIfInstruction(out condition, out exitBlockJump)
&& condition.MatchCompEqualsNull(out ldloc)
&& ldloc.MatchLdLoc(switchValueVar))
{
if (!defaultCase.Instructions[1].MatchBranch(out switchOnLengthBlock))
return false;
if (!exitBlockJump.MatchBranch(out defaultCase))
return false;
}
else
{
switchOnLengthBlock = defaultCase;
defaultCase = nullCase;
}
if (!MatchSwitchOnLengthBlock(switchValueVar, switchOnLengthBlock, out var blocksByLength))
return false;
List<(string, ILInstruction)> stringValues = new();
foreach (var block in blocksByLength)
{
if (block.Length.Count() != 1)
{
if (block.TargetBlock != nullCase)
return false;
}
else
{
int length = (int)block.Length.Intervals[0].Start;
if (MatchSwitchOnCharBlock(block.TargetBlock, length, switchValueVar, out var mapping)
|| MatchIfElseOnCharBlock(block.TargetBlock, length, switchValueVar, out mapping))
{
foreach (var item in mapping)
{
if (!stringValues.Any(x => x.Item1 == item.StringValue))
{
stringValues.Add(item);
}
else
{
return false;
}
}
}
else if (MatchRoslynCaseBlockHead(block.TargetBlock, switchValueVar, out var bodyOrLeave, out var exit, out string stringValue, out _))
{
if (exit != nullCase)
return false;
if (!stringValues.Any(x => x.Item1 == stringValue))
{
stringValues.Add((stringValue, bodyOrLeave));
}
else
{
return false;
}
}
else if (length == 0)
{
stringValues.Add(("", block.TargetBlock));
}
else
{
return false;
}
}
}
if (!stringValues.Any(pair => pair.Item1 == null))
{
if (IsNullCheckInDefaultBlock(ref defaultCase, switchValueVar, out var nullBlock))
{
stringValues.Add((null, nullBlock));
}
else if (nullCase != defaultCase)
{
stringValues.Add((null, nullCase));
}
}
context.Step(nameof(MatchRoslynSwitchOnStringUsingLengthAndChar), instructions[i]);
var defaultLabel = new LongSet(new LongInterval(0, stringValues.Count)).Invert();
var values = new string[stringValues.Count];
var sections = new SwitchSection[stringValues.Count];
foreach (var (idx, (value, bodyInstruction)) in stringValues.WithIndex())
{
values[idx] = value;
var body = bodyInstruction is Block b ? new Branch(b) : bodyInstruction;
sections[idx] = new SwitchSection { Labels = new LongSet(idx), Body = body };
}
var newSwitch = new SwitchInstruction(new StringToInt(new LdLoc(switchValueVar), values));
newSwitch.Sections.AddRange(sections);
newSwitch.Sections.Add(new SwitchSection { Labels = defaultLabel, Body = new Branch(defaultCase) });
newSwitch.AddILRange(instructions[i]);
newSwitch.AddILRange(instructions[i + 1]);
instructions[i].ReplaceWith(newSwitch);
instructions.RemoveAt(i + 1);
return true;
bool MatchGetChars(ILInstruction instruction, ILVariable switchValueVar, out int index)
{
index = -1;
return instruction is Call call
&& call.Method.FullNameIs("System.String", "get_Chars")
&& call.Arguments.Count == 2
&& call.Arguments[0].MatchLdLoc(switchValueVar)
&& call.Arguments[1].MatchLdcI4(out index);
}
bool MatchSwitchOnCharBlock(Block block, int length, ILVariable switchValueVar, out List<(string StringValue, ILInstruction BodyOrLeave)> results)
{
results = null;
if (block.IncomingEdgeCount != 1)
return false;
SwitchInstruction @switch;
int index;
switch (block.Instructions.Count)
{
case 1:
@switch = block.Instructions[0] as SwitchInstruction;
if (@switch == null)
return false;
if (!MatchGetChars(@switch.Value, switchValueVar, out index))
return false;
break;
case 2:
if (!block.Instructions[0].MatchStLoc(out var charTempVar, out var getCharsCall))
return false;
if (!MatchGetChars(getCharsCall, switchValueVar, out index))
return false;
if (index < 0)
return false;
@switch = block.Instructions[1] as SwitchInstruction;
if (@switch == null)
return false;
if (!@switch.Value.MatchLdLoc(charTempVar))
return false;
break;
default:
return false;
}
if (index >= length)
return false;
SwitchSection defaultSection = null;
foreach (var section in @switch.Sections)
{
if (section.Labels.Count() == 1)
{
char ch = unchecked((char)section.Labels.Values.Single());
if (!section.Body.MatchBranch(out var targetBlock))
return false;
if (length == 1)
{
results ??= new();
results.Add((ch.ToString(), targetBlock));
}
else
{
while (MatchRoslynCaseBlockHead(targetBlock, switchValueVar, out var bodyOrLeave, out var exit, out var stringValue, out _))
{
if (stringValue.Length != length || stringValue[index] != ch)
return false;
results ??= new();
results.Add((stringValue, bodyOrLeave));
if (exit == nullCase)
break;
targetBlock = exit;
}
}
}
else if (defaultSection == null)
{
defaultSection = section;
}
else
{
return false;
}
}
return results?.Count > 0;
}
bool MatchIfElseOnCharBlock(Block startOfChainBlock, int length, ILVariable switchValueVar, out List<(string StringValue, ILInstruction BodyOrLeave)> results)
{
results = null;
if (startOfChainBlock.IncomingEdgeCount != 1)
return false;
if (startOfChainBlock.Instructions.Count != 3)
return false;
if (!startOfChainBlock.Instructions[0].MatchStLoc(out var charTempVar, out var getCharsCall))
return false;
if (!MatchGetChars(getCharsCall, switchValueVar, out int index))
return false;
if (index < 0)
return false;
if (index >= length)
return false;
var currentBlock = startOfChainBlock;
int offset = 1;
while (true)
{
if (!currentBlock.Instructions[offset].MatchIfInstruction(out var condition, out var gotoHead))
break;
if (!condition.MatchCompEquals(out var left, out var right))
break;
if (!left.MatchLdLoc(charTempVar) || !right.MatchLdcI4(out int i))
break;
if (!currentBlock.Instructions[offset + 1].MatchBranch(out var nextBlock))
break;
if (!gotoHead.MatchBranch(out var headBlock))
break;
if (!MatchRoslynCaseBlockHead(headBlock, switchValueVar, out var bodyOrLeave, out var exit, out var stringValue, out _))
break;
if (exit != nullCase)
return false;
results ??= new();
results.Add((stringValue, bodyOrLeave));
offset = 0;
currentBlock = nextBlock;
}
return true;
}
bool MatchSwitchOnLengthBlock(ILVariable switchValueVar, Block switchOnLengthBlock, out List<(LongSet Length, Block TargetBlock)> blocks)
{
blocks = null;
if (switchOnLengthBlock.IncomingEdgeCount != 1)
return false;
SwitchInstruction @switch;
ILInstruction getLengthCall;
ILVariable lengthVar;
if (switchOnLengthBlock.FinalInstruction is not Nop)
return false;
switch (switchOnLengthBlock.Instructions.Count)
{
case 1:
@switch = switchOnLengthBlock.Instructions[0] as SwitchInstruction;
if (@switch == null)
return false;
getLengthCall = @switch.Value;
break;
case 2:
if (!switchOnLengthBlock.Instructions[0].MatchStLoc(out lengthVar, out getLengthCall))
return false;
@switch = switchOnLengthBlock.Instructions[1] as SwitchInstruction;
if (@switch == null)
return false;
if (!@switch.Value.MatchLdLoc(lengthVar))
return false;
break;
case 3:
@switch = null;
if (!switchOnLengthBlock.Instructions[0].MatchStLoc(out lengthVar, out getLengthCall))
return false;
if (!switchOnLengthBlock.Instructions[1].MatchIfInstruction(out var cond, out var gotoLength))
return false;
if (!gotoLength.MatchBranch(out var target))
return false;
if (!switchOnLengthBlock.Instructions[2].MatchBranch(out var gotoElse))
return false;
if (!cond.MatchCompEquals(out var lhs, out var rhs))
{
if (!cond.MatchCompNotEquals(out lhs, out rhs))
return false;
var t = target;
target = gotoElse;
gotoElse = t;
}
if (gotoElse != defaultCase)
return false;
if (!lhs.MatchLdLoc(lengthVar) || !rhs.MatchLdcI4(out int length))
return false;
blocks = new() {
(new LongSet(length), target),
(new LongSet(length).Invert(), defaultCase)
};
break;
default:
return false;
}
if (getLengthCall is not Call call
|| call.Arguments.Count != 1
|| !call.Method.FullNameIs("System.String", "get_Length"))
{
return false;
}
if (!call.Arguments[0].MatchLdLoc(switchValueVar))
return false;
if (@switch == null)
return true;
blocks = new(@switch.Sections.Count);
foreach (var section in @switch.Sections)
{
if (section.HasNullLabel)
return false;
if (!section.Body.MatchBranch(out var target))
return false;
if (section.Labels.Count() != 1)
{
if (!section.Body.MatchBranch(defaultCase))
return false;
}
else
{
blocks.Add((section.Labels, target));
}
}
return true;
}
}
/// <summary>
/// Matches:
/// Block oldDefaultBlock (incoming: 1) {

6
packages.props

@ -27,10 +27,10 @@ @@ -27,10 +27,10 @@
<MoqVersion>4.18.4</MoqVersion>
<DiffLibVersion>2017.7.26.1241</DiffLibVersion>
<!-- Microsoft.CodeAnalysis.* -->
<RoslynVersion>4.4.0</RoslynVersion>
<RoslynVersion>4.6.0</RoslynVersion>
<!-- Microsoft.NETCore.ILAsm -->
<ILAsmVersion>6.0.0</ILAsmVersion>
<ILAsmVersion>7.0.0</ILAsmVersion>
<!-- Microsoft.NETCore.ILDAsm -->
<ILDAsmVersion>6.0.0</ILDAsmVersion>
<ILDAsmVersion>7.0.0</ILDAsmVersion>
</PropertyGroup>
</Project>

Loading…
Cancel
Save