Browse Source

Implement support for C# 11 switch on (ReadOnly)Span<char>.

pull/3058/head
Siegfried Pammer 2 years ago
parent
commit
70616b301c
  1. 5
      ICSharpCode.Decompiler.Tests/Helpers/Tester.VB.cs
  2. 1
      ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
  3. 49
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.cs
  4. 2
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  5. 2
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  6. 18
      ICSharpCode.Decompiler/DecompilerSettings.cs
  7. 15
      ICSharpCode.Decompiler/IL/Instructions/StringToInt.cs
  8. 209
      ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs
  9. 9
      ILSpy/Properties/Resources.Designer.cs
  10. 3
      ILSpy/Properties/Resources.resx

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

@ -60,13 +60,16 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
var vbcPath = roslynToolset.GetVBCompiler(roslynVersion); var vbcPath = roslynToolset.GetVBCompiler(roslynVersion);
IEnumerable<string> references; IEnumerable<string> references;
string libPath;
if ((flags & CompilerOptions.UseRoslynMask) != 0 && (flags & CompilerOptions.TargetNet40) == 0) if ((flags & CompilerOptions.UseRoslynMask) != 0 && (flags & CompilerOptions.TargetNet40) == 0)
{ {
references = coreDefaultReferences.Select(r => "-r:\"" + r + "\""); references = coreDefaultReferences.Select(r => "-r:\"" + r + "\"");
libPath = coreRefAsmPath;
} }
else else
{ {
references = defaultReferences.Select(r => "-r:\"" + r + "\""); references = defaultReferences.Select(r => "-r:\"" + r + "\"");
libPath = RefAsmPath;
} }
if (flags.HasFlag(CompilerOptions.ReferenceVisualBasic)) if (flags.HasFlag(CompilerOptions.ReferenceVisualBasic))
{ {
@ -116,7 +119,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
} }
var command = Cli.Wrap(vbcPath) var command = Cli.Wrap(vbcPath)
.WithArguments($"{otherOptions}{string.Join(" ", references)} -out:\"{Path.GetFullPath(results.PathToAssembly)}\" {string.Join(" ", sourceFileNames.Select(fn => '"' + Path.GetFullPath(fn) + '"'))}") .WithArguments($"{otherOptions}-libpath:\"{libPath}\" {string.Join(" ", references)} -out:\"{Path.GetFullPath(results.PathToAssembly)}\" {string.Join(" ", sourceFileNames.Select(fn => '"' + Path.GetFullPath(fn) + '"'))}")
.WithValidation(CommandResultValidation.None); .WithValidation(CommandResultValidation.None);
Console.WriteLine($"\"{command.TargetFilePath}\" {command.Arguments}"); Console.WriteLine($"\"{command.TargetFilePath}\" {command.Arguments}");

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

@ -295,6 +295,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
"System.Linq.Expressions.dll", "System.Linq.Expressions.dll",
"System.Linq.Queryable.dll", "System.Linq.Queryable.dll",
"System.IO.FileSystem.Watcher.dll", "System.IO.FileSystem.Watcher.dll",
"System.Memory.dll",
"System.Threading.dll", "System.Threading.dll",
"System.Threading.Thread.dll", "System.Threading.Thread.dll",
"System.Runtime.dll", "System.Runtime.dll",

49
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.cs

@ -1509,5 +1509,54 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
break; break;
} }
} }
#if CS110 && NET70
public static string SwitchOverReadOnlySpanChar1(ReadOnlySpan<char> text)
{
Console.WriteLine("SwitchOverReadOnlySpanChar1:");
switch (text)
{
case "First case":
return "Text1";
case "Second case":
case "2nd case":
return "Text2";
case "Third case":
return "Text3";
case "Fourth case":
return "Text4";
case "Fifth case":
return "Text5";
case "Sixth case":
return "Text6";
default:
return "Default";
}
}
public static string SwitchOverSpanChar1(Span<char> text)
{
Console.WriteLine("SwitchOverSpanChar1:");
switch (text)
{
case "First case":
return "Text1";
case "Second case":
case "2nd case":
return "Text2";
case "Third case":
return "Text3";
case "Fourth case":
return "Text4";
case "Fifth case":
return "Text5";
case "Sixth case":
return "Text6";
default:
return "Default";
}
}
#endif
} }
} }

2
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -3898,7 +3898,7 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
value = Translate(strToInt.Argument) value = Translate(strToInt.Argument)
.ConvertTo( .ConvertTo(
typeSystem.FindType(KnownTypeCode.String), strToInt.ExpectedType,
this, this,
allowImplicitConversion: false // switch-expression does not support implicit conversions allowImplicitConversion: false // switch-expression does not support implicit conversions
); );

2
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -214,7 +214,7 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
value = exprBuilder.Translate(strToInt.Argument) value = exprBuilder.Translate(strToInt.Argument)
.ConvertTo( .ConvertTo(
typeSystem.FindType(KnownTypeCode.String), strToInt.ExpectedType,
exprBuilder, exprBuilder,
// switch statement does support implicit conversions in general, however, the rules are // switch statement does support implicit conversions in general, however, the rules are
// not very intuitive and in order to prevent bugs, we emit an explicit cast. // not very intuitive and in order to prevent bugs, we emit an explicit cast.

18
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -1206,6 +1206,24 @@ namespace ICSharpCode.Decompiler
} }
} }
bool switchOnReadOnlySpanChar = true;
/// <summary>
/// Gets/Sets whether to use C# 11.0 switch on (ReadOnly)Span&lt;char&gt;
/// </summary>
[Category("C# 11.0 / VS 2022.4")]
[Description("DecompilerSettings.SwitchOnReadOnlySpanChar")]
public bool SwitchOnReadOnlySpanChar {
get { return switchOnReadOnlySpanChar; }
set {
if (switchOnReadOnlySpanChar != value)
{
switchOnReadOnlySpanChar = value;
OnPropertyChanged();
}
}
}
bool unsignedRightShift = true; bool unsignedRightShift = true;
/// <summary> /// <summary>

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

@ -20,21 +20,26 @@
using System.Collections.Generic; using System.Collections.Generic;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.IL namespace ICSharpCode.Decompiler.IL
{ {
partial class StringToInt partial class StringToInt
{ {
public List<(string? Key, int Value)> Map { get; } public List<(string? Key, int Value)> Map { get; }
public StringToInt(ILInstruction argument, List<(string? Key, int Value)> map) public IType ExpectedType { get; }
public StringToInt(ILInstruction argument, List<(string? Key, int Value)> map, IType expectedType)
: base(OpCode.StringToInt) : base(OpCode.StringToInt)
{ {
this.Argument = argument; this.Argument = argument;
this.Map = map; this.Map = map;
this.ExpectedType = expectedType;
} }
public StringToInt(ILInstruction argument, string?[] map) public StringToInt(ILInstruction argument, string?[] map, IType expectedType)
: this(argument, ArrayToDictionary(map)) : this(argument, ArrayToDictionary(map), expectedType)
{ {
} }
@ -51,7 +56,9 @@ namespace ICSharpCode.Decompiler.IL
public override void WriteTo(ITextOutput output, ILAstWritingOptions options) public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{ {
WriteILRange(output, options); WriteILRange(output, options);
output.Write("string.to.int ("); output.Write("string.to.int ");
ExpectedType.WriteTo(output);
output.Write('(');
Argument.WriteTo(output, options); Argument.WriteTo(output, options);
output.Write(", { "); output.Write(", { ");
int i = 0; int i = 0;

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

@ -78,7 +78,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
changed = true; changed = true;
continue; continue;
} }
if (MatchRoslynSwitchOnStringUsingLengthAndChar(block.Instructions, ref i)) if (MatchRoslynSwitchOnStringUsingLengthAndChar(block, i))
{ {
changed = true; changed = true;
continue; continue;
@ -334,7 +334,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
int offset = firstBlock == null ? 1 : 0; int offset = firstBlock == null ? 1 : 0;
var sections = new List<SwitchSection>(values.Skip(offset).SelectWithIndex((index, s) => new SwitchSection { Labels = new LongSet(index), Body = s.Item2 is Block b ? new Branch(b) : s.Item2.Clone() })); var sections = new List<SwitchSection>(values.Skip(offset).SelectWithIndex((index, s) => new SwitchSection { Labels = new LongSet(index), Body = s.Item2 is Block b ? new Branch(b) : s.Item2.Clone() }));
sections.Add(new SwitchSection { Labels = new LongSet(new LongInterval(0, sections.Count)).Invert(), Body = currentCaseBlock != null ? (ILInstruction)new Branch(currentCaseBlock) : new Leave((BlockContainer)nextCaseBlock) }); sections.Add(new SwitchSection { Labels = new LongSet(new LongInterval(0, sections.Count)).Invert(), Body = currentCaseBlock != null ? (ILInstruction)new Branch(currentCaseBlock) : new Leave((BlockContainer)nextCaseBlock) });
var stringToInt = new StringToInt(switchValue, values.Skip(offset).Select(item => item.Item1).ToArray()); var stringToInt = new StringToInt(switchValue, values.Skip(offset).Select(item => item.Item1).ToArray(), context.TypeSystem.FindType(KnownTypeCode.String));
var inst = new SwitchInstruction(stringToInt); var inst = new SwitchInstruction(stringToInt);
inst.Sections.AddRange(sections); inst.Sections.AddRange(sections);
if (removeExtraLoad) if (removeExtraLoad)
@ -450,7 +450,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var sections = new List<SwitchSection>(values.SelectWithIndex((index, b) => new SwitchSection { Labels = new LongSet(index), Body = b.Item2 })); var sections = new List<SwitchSection>(values.SelectWithIndex((index, b) => new SwitchSection { Labels = new LongSet(index), Body = b.Item2 }));
sections.Add(new SwitchSection { Labels = new LongSet(new LongInterval(0, sections.Count)).Invert(), Body = new Branch(currentCaseBlock) }); sections.Add(new SwitchSection { Labels = new LongSet(new LongInterval(0, sections.Count)).Invert(), Body = new Branch(currentCaseBlock) });
var stringToInt = new StringToInt(switchValue, values.SelectArray(item => item.Item1)); var stringToInt = new StringToInt(switchValue, values.SelectArray(item => item.Item1), context.TypeSystem.FindType(KnownTypeCode.String));
var inst = new SwitchInstruction(stringToInt); var inst = new SwitchInstruction(stringToInt);
inst.Sections.AddRange(sections); inst.Sections.AddRange(sections);
@ -671,7 +671,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
switchValue = new LdLoc(switchValueVar); switchValue = new LdLoc(switchValueVar);
keepAssignmentBefore = true; keepAssignmentBefore = true;
} }
var stringToInt = new StringToInt(switchValue, stringValues); var stringToInt = new StringToInt(switchValue, stringValues, switchValueVar.Type);
var inst = new SwitchInstruction(stringToInt); var inst = new SwitchInstruction(stringToInt);
inst.Sections.AddRange(sections); inst.Sections.AddRange(sections);
instructions[i + 1].ReplaceWith(inst); instructions[i + 1].ReplaceWith(inst);
@ -860,7 +860,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// stloc tmp(ldloc switch-value) // stloc tmp(ldloc switch-value)
// stloc switchVariable(ldloc tmp) // stloc switchVariable(ldloc tmp)
// if (comp(ldloc tmp == ldnull)) br nullCaseBlock // if (comp(ldloc tmp == ldnull)) br nullCaseBlock
// br getItemBloc // br getItemBlock
if (block.Instructions.Count != i + 4) if (block.Instructions.Count != i + 4)
return false; return false;
if (!block.Instructions[i].MatchStLoc(out var tmp, out var switchValue)) if (!block.Instructions[i].MatchStLoc(out var tmp, out var switchValue))
@ -919,7 +919,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
} }
context.Step(nameof(MatchLegacySwitchOnStringWithHashtable), block.Instructions[i]); context.Step(nameof(MatchLegacySwitchOnStringWithHashtable), block.Instructions[i]);
var stringToInt = new StringToInt(switchValue, stringValues); var stringToInt = new StringToInt(switchValue, stringValues, context.TypeSystem.FindType(KnownTypeCode.String));
var inst = new SwitchInstruction(stringToInt); var inst = new SwitchInstruction(stringToInt);
inst.Sections.AddRange(sections); inst.Sections.AddRange(sections);
inst.AddILRange(block.Instructions[i]); inst.AddILRange(block.Instructions[i]);
@ -998,15 +998,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms
switchBlockInstructions = nextBlockJump.TargetBlock.Instructions; switchBlockInstructions = nextBlockJump.TargetBlock.Instructions;
switchBlockInstructionsOffset = 0; switchBlockInstructionsOffset = 0;
} }
// stloc switchValueVar(call ComputeStringHash(switchValue)) // stloc switchValueVar(call ComputeStringHash(switchValueLoad))
// switch (ldloc switchValueVar) { // switch (ldloc switchValueVar) {
// case [211455823..211455824): br caseBlock1 // case [211455823..211455824): br caseBlock1
// ... more cases ... // ... more cases ...
// case [long.MinValue..-365098645),...,[1697255802..long.MaxValue]: br defaultBlock // case [long.MinValue..-365098645),...,[1697255802..long.MaxValue]: br defaultBlock
// } // }
if (!(switchBlockInstructionsOffset + 1 < switchBlockInstructions.Count && switchBlockInstructions[switchBlockInstructionsOffset + 1] is SwitchInstruction switchInst && switchInst.Value.MatchLdLoc(out var switchValueVar) && if (!(switchBlockInstructionsOffset + 1 < switchBlockInstructions.Count
MatchComputeStringHashCall(switchBlockInstructions[switchBlockInstructionsOffset], switchValueVar, out LdLoc switchValueLoad))) && switchBlockInstructions[switchBlockInstructionsOffset + 1] is SwitchInstruction switchInst
&& switchInst.Value.MatchLdLoc(out var switchValueVar)
&& MatchComputeStringHashCall(switchBlockInstructions[switchBlockInstructionsOffset],
switchValueVar, out LdLoc switchValueLoad)))
{
return false; return false;
}
if (instForNullCheck != null && !instForNullCheck.MatchLdLoc(switchValueLoad.Variable)) if (instForNullCheck != null && !instForNullCheck.MatchLdLoc(switchValueLoad.Variable))
{ {
@ -1137,7 +1142,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var body = bodyInstruction is Block b ? new Branch(b) : bodyInstruction; var body = bodyInstruction is Block b ? new Branch(b) : bodyInstruction;
sections[idx] = new SwitchSection { Labels = new LongSet(idx), Body = body }; sections[idx] = new SwitchSection { Labels = new LongSet(idx), Body = body };
} }
var newSwitch = new SwitchInstruction(new StringToInt(switchValueInst, values)); var newSwitch = new SwitchInstruction(new StringToInt(switchValueInst, values, switchValueLoad.Variable.Type));
newSwitch.Sections.AddRange(sections); newSwitch.Sections.AddRange(sections);
newSwitch.Sections.Add(new SwitchSection { Labels = defaultLabel, Body = defaultSection.Body }); newSwitch.Sections.Add(new SwitchSection { Labels = defaultLabel, Body = defaultSection.Body });
instructions[offset].ReplaceWith(newSwitch); instructions[offset].ReplaceWith(newSwitch);
@ -1145,54 +1150,63 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
} }
private bool MatchRoslynSwitchOnStringUsingLengthAndChar(InstructionCollection<ILInstruction> instructions, ref int i) private bool MatchRoslynSwitchOnStringUsingLengthAndChar(Block block, int i)
{ {
var instructions = block.Instructions;
// implements https://github.com/dotnet/roslyn/pull/66081 // implements https://github.com/dotnet/roslyn/pull/66081
if (i >= instructions.Count - 1)
return false;
// if (comp(ldloc switchValueVar == ldnull)) br nullCase // if (comp(ldloc switchValueVar == ldnull)) br nullCase
// br nextBlock // 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; Block switchOnLengthBlock;
if (defaultCase.IncomingEdgeCount == 1 int switchOnLengthBlockStartOffset;
&& defaultCase.Instructions[0].MatchIfInstruction(out condition, out exitBlockJump) Block nullCase = null;
&& condition.MatchCompEqualsNull(out ldloc) if (instructions[i].MatchIfInstruction(out var condition, out var exitBlockJump)
&& ldloc.MatchLdLoc(switchValueVar)) && condition.MatchCompEqualsNull(out var ldloc)
&& ldloc is LdLoc { Variable: var switchValueVar })
{ {
if (!defaultCase.Instructions[1].MatchBranch(out switchOnLengthBlock)) if (!instructions[i + 1].MatchBranch(out var nextBlock))
return false;
if (!exitBlockJump.MatchBranch(out nullCase))
return false; return false;
if (!exitBlockJump.MatchBranch(out defaultCase)) // if (comp(ldloc switchValueVar == ldnull)) br ...
// br switchOnLengthBlock
if (nextBlock.IncomingEdgeCount == 1
&& nextBlock.Instructions[0].MatchIfInstruction(out condition, out _)
&& condition.MatchCompEqualsNull(out ldloc)
&& ldloc.MatchLdLoc(switchValueVar))
{
if (!nextBlock.Instructions[1].MatchBranch(out switchOnLengthBlock))
return false;
}
else
{
switchOnLengthBlock = nextBlock;
}
if (switchOnLengthBlock.IncomingEdgeCount != 1)
return false; return false;
switchOnLengthBlockStartOffset = 0;
} }
else else
{ {
switchOnLengthBlock = defaultCase; switchOnLengthBlock = block;
defaultCase = nullCase; switchValueVar = null; // will be extracted in MatchSwitchOnLengthBlock
switchOnLengthBlockStartOffset = i;
} }
if (!MatchSwitchOnLengthBlock(switchValueVar, switchOnLengthBlock, out var blocksByLength)) Block defaultCase = null;
if (!MatchSwitchOnLengthBlock(ref switchValueVar, switchOnLengthBlock, switchOnLengthBlockStartOffset, out var blocksByLength))
return false; return false;
List<(string, ILInstruction)> stringValues = new(); List<(string, ILInstruction)> stringValues = new();
foreach (var block in blocksByLength) foreach (var b in blocksByLength)
{ {
if (block.Length.Count() != 1) if (b.Length.Count() != 1)
{ {
if (block.TargetBlock != nullCase) if (b.TargetBlock != nullCase)
return false; return false;
} }
else else
{ {
int length = (int)block.Length.Intervals[0].Start; int length = (int)b.Length.Intervals[0].Start;
if (MatchSwitchOnCharBlock(block.TargetBlock, length, switchValueVar, out var mapping) if (MatchSwitchOnCharBlock(b.TargetBlock, length, switchValueVar, out var mapping)
|| MatchIfElseOnCharBlock(block.TargetBlock, length, switchValueVar, out mapping)) || MatchIfElseOnCharBlock(b.TargetBlock, length, switchValueVar, out mapping))
{ {
foreach (var item in mapping) foreach (var item in mapping)
{ {
@ -1206,9 +1220,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
} }
} }
else if (MatchRoslynCaseBlockHead(block.TargetBlock, switchValueVar, out var bodyOrLeave, out var exit, out string stringValue, out _)) else if (MatchRoslynCaseBlockHead(b.TargetBlock, switchValueVar, out var bodyOrLeave, out var exit, out string stringValue, out _))
{ {
if (exit != nullCase) if (exit != defaultCase)
return false; return false;
if (!stringValues.Any(x => x.Item1 == stringValue)) if (!stringValues.Any(x => x.Item1 == stringValue))
{ {
@ -1221,7 +1235,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
else if (length == 0) else if (length == 0)
{ {
stringValues.Add(("", block.TargetBlock)); stringValues.Add(("", b.TargetBlock));
} }
else else
{ {
@ -1236,7 +1250,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
stringValues.Add((null, nullBlock)); stringValues.Add((null, nullBlock));
} }
else if (nullCase != defaultCase) else if (nullCase != null && nullCase != defaultCase)
{ {
stringValues.Add((null, nullCase)); stringValues.Add((null, nullCase));
} }
@ -1252,23 +1266,38 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var body = bodyInstruction is Block b ? new Branch(b) : bodyInstruction; var body = bodyInstruction is Block b ? new Branch(b) : bodyInstruction;
sections[idx] = new SwitchSection { Labels = new LongSet(idx), Body = body }; sections[idx] = new SwitchSection { Labels = new LongSet(idx), Body = body };
} }
var newSwitch = new SwitchInstruction(new StringToInt(new LdLoc(switchValueVar), values)); var newSwitch = new SwitchInstruction(new StringToInt(new LdLoc(switchValueVar), values, switchValueVar.Type));
newSwitch.Sections.AddRange(sections); newSwitch.Sections.AddRange(sections);
newSwitch.Sections.Add(new SwitchSection { Labels = defaultLabel, Body = new Branch(defaultCase) }); newSwitch.Sections.Add(new SwitchSection { Labels = defaultLabel, Body = new Branch(defaultCase) });
newSwitch.AddILRange(instructions[i]); newSwitch.AddILRange(instructions[i]);
newSwitch.AddILRange(instructions[i + 1]); if (nullCase != null)
instructions[i].ReplaceWith(newSwitch); {
instructions.RemoveAt(i + 1); newSwitch.AddILRange(instructions[i + 1]);
}
instructions[i] = newSwitch;
instructions.RemoveRange(i + 1, instructions.Count - (i + 1));
return true; return true;
bool MatchGetChars(ILInstruction instruction, ILVariable switchValueVar, out int index) bool MatchGetChars(ILInstruction instruction, ILVariable switchValueVar, out int index)
{ {
index = -1; index = -1;
return instruction is Call call if (context.Settings.SwitchOnReadOnlySpanChar && instruction.MatchLdObj(out var target, out var type) && type.IsKnownType(KnownTypeCode.UInt16))
&& call.Method.FullNameIs("System.String", "get_Chars") {
&& call.Arguments.Count == 2 return target is Call call
&& call.Arguments[0].MatchLdLoc(switchValueVar) && (call.Method.FullNameIs("System.ReadOnlySpan", "get_Item")
&& call.Arguments[1].MatchLdcI4(out index); || call.Method.FullNameIs("System.Span", "get_Item"))
&& call.Arguments.Count == 2
&& call.Arguments[0].MatchLdLoca(switchValueVar)
&& call.Arguments[1].MatchLdcI4(out index);
}
else
{
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) bool MatchSwitchOnCharBlock(Block block, int length, ILVariable switchValueVar, out List<(string StringValue, ILInstruction BodyOrLeave)> results)
@ -1375,7 +1404,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
break; break;
if (!MatchRoslynCaseBlockHead(headBlock, switchValueVar, out var bodyOrLeave, out var exit, out var stringValue, out _)) if (!MatchRoslynCaseBlockHead(headBlock, switchValueVar, out var bodyOrLeave, out var exit, out var stringValue, out _))
break; break;
if (exit != nullCase) if (exit != defaultCase)
return false; return false;
results ??= new(); results ??= new();
results.Add((stringValue, bodyOrLeave)); results.Add((stringValue, bodyOrLeave));
@ -1385,28 +1414,24 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; return true;
} }
bool MatchSwitchOnLengthBlock(ILVariable switchValueVar, Block switchOnLengthBlock, out List<(LongSet Length, Block TargetBlock)> blocks) bool MatchSwitchOnLengthBlock(ref ILVariable switchValueVar, Block switchOnLengthBlock, int startOffset, out List<(LongSet Length, Block TargetBlock)> blocks)
{ {
blocks = null; blocks = null;
if (switchOnLengthBlock.IncomingEdgeCount != 1)
return false;
SwitchInstruction @switch; SwitchInstruction @switch;
ILInstruction getLengthCall; ILInstruction getLengthCall;
ILVariable lengthVar; ILVariable lengthVar;
if (switchOnLengthBlock.FinalInstruction is not Nop) switch (switchOnLengthBlock.Instructions.Count - startOffset)
return false;
switch (switchOnLengthBlock.Instructions.Count)
{ {
case 1: case 1:
@switch = switchOnLengthBlock.Instructions[0] as SwitchInstruction; @switch = switchOnLengthBlock.Instructions[startOffset] as SwitchInstruction;
if (@switch == null) if (@switch == null)
return false; return false;
getLengthCall = @switch.Value; getLengthCall = @switch.Value;
break; break;
case 2: case 2:
if (!switchOnLengthBlock.Instructions[0].MatchStLoc(out lengthVar, out getLengthCall)) if (!switchOnLengthBlock.Instructions[startOffset].MatchStLoc(out lengthVar, out getLengthCall))
return false; return false;
@switch = switchOnLengthBlock.Instructions[1] as SwitchInstruction; @switch = switchOnLengthBlock.Instructions[startOffset + 1] as SwitchInstruction;
if (@switch == null) if (@switch == null)
return false; return false;
if (!@switch.Value.MatchLdLoc(lengthVar)) if (!@switch.Value.MatchLdLoc(lengthVar))
@ -1414,13 +1439,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
break; break;
case 3: case 3:
@switch = null; @switch = null;
if (!switchOnLengthBlock.Instructions[0].MatchStLoc(out lengthVar, out getLengthCall)) if (!switchOnLengthBlock.Instructions[startOffset].MatchStLoc(out lengthVar, out getLengthCall))
return false; return false;
if (!switchOnLengthBlock.Instructions[1].MatchIfInstruction(out var cond, out var gotoLength)) if (!switchOnLengthBlock.Instructions[startOffset + 1].MatchIfInstruction(out var cond, out var gotoLength))
return false; return false;
if (!gotoLength.MatchBranch(out var target)) if (!gotoLength.MatchBranch(out var target))
return false; return false;
if (!switchOnLengthBlock.Instructions[2].MatchBranch(out var gotoElse)) if (!switchOnLengthBlock.Instructions[startOffset + 2].MatchBranch(out var gotoElse))
return false; return false;
if (!cond.MatchCompEquals(out var lhs, out var rhs)) if (!cond.MatchCompEquals(out var lhs, out var rhs))
{ {
@ -1430,8 +1455,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
target = gotoElse; target = gotoElse;
gotoElse = t; gotoElse = t;
} }
if (gotoElse != defaultCase) defaultCase = gotoElse;
return false;
if (!lhs.MatchLdLoc(lengthVar) || !rhs.MatchLdcI4(out int length)) if (!lhs.MatchLdLoc(lengthVar) || !rhs.MatchLdcI4(out int length))
return false; return false;
@ -1445,12 +1469,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
if (getLengthCall is not Call call if (getLengthCall is not Call call
|| call.Arguments.Count != 1 || call.Arguments.Count != 1
|| !call.Method.FullNameIs("System.String", "get_Length")) || call.Method.Name != "get_Length")
{ {
return false; return false;
} }
if (!call.Arguments[0].MatchLdLoc(switchValueVar)) var declaringTypeCode = call.Method.DeclaringTypeDefinition?.KnownTypeCode;
return false; switch (declaringTypeCode)
{
case KnownTypeCode.String:
if (!call.Arguments[0].MatchLdLoc(switchValueVar))
return false;
break;
case KnownTypeCode.ReadOnlySpanOfT:
case KnownTypeCode.SpanOfT:
if (!context.Settings.SwitchOnReadOnlySpanChar)
return false;
if (!call.Arguments[0].MatchLdLoca(out switchValueVar))
return false;
break;
default:
return false;
}
if (@switch == null) if (@switch == null)
return true; return true;
blocks = new(@switch.Sections.Count); blocks = new(@switch.Sections.Count);
@ -1462,7 +1501,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
if (section.Labels.Count() != 1) if (section.Labels.Count() != 1)
{ {
if (!section.Body.MatchBranch(defaultCase)) defaultCase ??= target;
if (defaultCase != target)
return false; return false;
} }
else else
@ -1644,8 +1684,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
/// <summary> /// <summary>
/// Matches 'call string.op_Equality(ldloc(variable), ldstr(stringValue))' /// Matches 'call string.op_Equality(ldloc(variable), ldstr stringValue)'
/// or 'comp(ldloc(variable) == ldnull)' /// or 'comp(ldloc(variable) == ldnull)'
/// or 'call SequenceEqual(ldloc variable, call AsSpan(ldstr stringValue))'
/// </summary> /// </summary>
bool MatchStringEqualityComparison(ILInstruction condition, ILVariable variable, out string stringValue, out bool isVBCompareString) bool MatchStringEqualityComparison(ILInstruction condition, ILVariable variable, out string stringValue, out bool isVBCompareString)
{ {
@ -1653,8 +1694,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
/// <summary> /// <summary>
/// Matches 'call string.op_Equality(ldloc(variable), ldstr(stringValue))' /// Matches 'call string.op_Equality(ldloc(variable), ldstr stringValue)'
/// or 'comp(ldloc(variable) == ldnull)' /// or 'comp(ldloc(variable) == ldnull)'
/// or 'call SequenceEqual(ldloc variable, call AsSpan(ldstr stringValue))'
/// </summary> /// </summary>
bool MatchStringEqualityComparison(ILInstruction condition, out ILVariable variable, out string stringValue, out bool isVBCompareString) bool MatchStringEqualityComparison(ILInstruction condition, out ILVariable variable, out string stringValue, out bool isVBCompareString)
{ {
@ -1676,8 +1718,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
right = c.Arguments[1]; right = c.Arguments[1];
} }
else if (c.Method.IsStatic && c.Method.Name == "CompareString" else if (c.Method.IsStatic && c.Method.Name == "CompareString"
&& c.Method.DeclaringType.FullName == "Microsoft.VisualBasic.CompilerServices.Operators" && c.Method.DeclaringType.FullName == "Microsoft.VisualBasic.CompilerServices.Operators"
&& c.Arguments.Count == 3) && c.Arguments.Count == 3)
{ {
left = c.Arguments[0]; left = c.Arguments[0];
right = c.Arguments[1]; right = c.Arguments[1];
@ -1690,6 +1732,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
} }
} }
else if (c.Method.IsStatic && c.Method.Name == "SequenceEqual"
&& c.Method.DeclaringType.FullName == "System.MemoryExtensions"
&& c.Arguments.Count == 2)
{
left = c.Arguments[0];
if (c.Arguments[1] is Call {
Method.IsStatic: true,
Method.Name: "AsSpan",
Method.DeclaringType.FullName: "System.MemoryExtensions",
Arguments: [var ldStr]
} asSpanCall)
{
right = ldStr;
}
else
{
return false;
}
}
else else
{ {
return false; return false;

9
ILSpy/Properties/Resources.Designer.cs generated

@ -1316,6 +1316,15 @@ namespace ICSharpCode.ILSpy.Properties {
} }
} }
/// <summary>
/// Looks up a localized string similar to Use switch on (ReadOnly)Span&lt;char&gt;.
/// </summary>
public static string DecompilerSettings_SwitchOnReadOnlySpanChar {
get {
return ResourceManager.GetString("DecompilerSettings.SwitchOnReadOnlySpanChar", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Unsigned right shift (&gt;&gt;&gt;). /// Looks up a localized string similar to Unsigned right shift (&gt;&gt;&gt;).
/// </summary> /// </summary>

3
ILSpy/Properties/Resources.resx

@ -462,6 +462,9 @@ Are you sure you want to continue?</value>
<data name="DecompilerSettings.SwitchExpressions" xml:space="preserve"> <data name="DecompilerSettings.SwitchExpressions" xml:space="preserve">
<value>Switch expressions</value> <value>Switch expressions</value>
</data> </data>
<data name="DecompilerSettings.SwitchOnReadOnlySpanChar" xml:space="preserve">
<value>Use switch on (ReadOnly)Span&lt;char&gt;</value>
</data>
<data name="DecompilerSettings.UnsignedRightShift" xml:space="preserve"> <data name="DecompilerSettings.UnsignedRightShift" xml:space="preserve">
<value>Unsigned right shift (&gt;&gt;&gt;)</value> <value>Unsigned right shift (&gt;&gt;&gt;)</value>
</data> </data>

Loading…
Cancel
Save