Skip to content

Commit

Permalink
Fix switch-on-string transform for optimized Roslyn.
Browse files Browse the repository at this point in the history
  • Loading branch information
siegfriedpammer committed Jan 5, 2025
1 parent e4285b7 commit e1e2f73
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 41 deletions.
100 changes: 97 additions & 3 deletions ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,46 @@ public static implicit operator int(ImplicitInt v)
}
}

public class ImplicitConversionConflictWithLong
{
private readonly int s;

public ImplicitConversionConflictWithLong(int s)
{
this.s = s;
}

public static implicit operator int(ImplicitConversionConflictWithLong v)
{
return v.s;
}

public static implicit operator long(ImplicitConversionConflictWithLong v)
{
return v.s;
}
}

public class ImplicitConversionConflictWithString
{
private readonly int s;

public ImplicitConversionConflictWithString(int s)
{
this.s = s;
}

public static implicit operator int(ImplicitConversionConflictWithString v)
{
return v.s;
}

public static implicit operator string(ImplicitConversionConflictWithString v)
{
return string.Empty;
}
}

public class ExplicitInt
{
private readonly int s;
Expand Down Expand Up @@ -396,6 +436,62 @@ public static void SwitchOverImplicitInt(ImplicitInt i)
}
}

public static void SwitchOverImplicitIntConflictLong(ImplicitConversionConflictWithLong i)
{
switch ((int)i)
{
case 0:
Console.WriteLine("zero");
break;
case 5:
Console.WriteLine("five");
break;
case 10:
Console.WriteLine("ten");
break;
case 15:
Console.WriteLine("fifteen");
break;
case 20:
Console.WriteLine("twenty");
break;
case 25:
Console.WriteLine("twenty-five");
break;
case 30:
Console.WriteLine("thirty");
break;
}
}

public static void SwitchOverImplicitIntConflictString(ImplicitConversionConflictWithString i)
{
switch ((string)i)
{
case "0":
Console.WriteLine("zero");
break;
case "5":
Console.WriteLine("five");
break;
case "10":
Console.WriteLine("ten");
break;
case "15":
Console.WriteLine("fifteen");
break;
case "20":
Console.WriteLine("twenty");
break;
case "25":
Console.WriteLine("twenty-five");
break;
case "30":
Console.WriteLine("thirty");
break;
}
}

// SwitchDetection.UseCSharpSwitch requires more complex heuristic to identify this when compiled with Roslyn
public static void CompactSwitchOverInt(int i)
{
Expand Down Expand Up @@ -507,9 +603,7 @@ public static string SwitchOverString2()

public static string SwitchOverImplicitString(ImplicitString s)
{
// we emit an explicit cast, because the rules used by the C# compiler are counter-intuitive:
// The C# compiler does *not* take the type of the switch labels into account at all.
switch ((string)s)
switch (s)
{
case "First case":
return "Text1";
Expand Down
89 changes: 51 additions & 38 deletions ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1070,10 +1070,12 @@ bool MatchRoslynSwitchOnString(InstructionCollection<ILInstruction> instructions
stringValues.Add((null, nullValueCaseBlock));
}
// In newer Roslyn versions (>=3.7) the null check appears in the default case, not prior to the switch.
if (!stringValues.Any(pair => pair.Value == null) && IsNullCheckInDefaultBlock(ref exitOrDefaultBlock, switchValueLoad.Variable, out nullValueCaseBlock))
ILInstruction exitOrDefault = exitOrDefaultBlock;
if (!stringValues.Any(pair => pair.Value == null) && IsNullCheckInDefaultBlock(ref exitOrDefault, switchValueLoad.Variable, out nullValueCaseBlock))
{
stringValues.Add((null, nullValueCaseBlock));
}
exitOrDefaultBlock = (Block)exitOrDefault;

context.Step(nameof(MatchRoslynSwitchOnString), switchValueLoad);
if (exitOrDefaultBlock != null)
Expand Down Expand Up @@ -1176,7 +1178,7 @@ private bool MatchRoslynSwitchOnStringUsingLengthAndChar(Block block, int i)
{
if (!instructions[i + 1].MatchBranch(out var nextBlock))
return false;
if (!exitBlockJump.MatchBranch(out nullCase))
if (!exitBlockJump.MatchBranch(out nullCase) && !exitBlockJump.MatchLeave(out _))
return false;
// if (comp(ldloc switchValueVar == ldnull)) br ...
// br switchOnLengthBlock
Expand All @@ -1202,7 +1204,7 @@ private bool MatchRoslynSwitchOnStringUsingLengthAndChar(Block block, int i)
switchValueVar = null; // will be extracted in MatchSwitchOnLengthBlock
switchOnLengthBlockStartOffset = i;
}
Block defaultCase = null;
ILInstruction defaultCase = null;
if (!MatchSwitchOnLengthBlock(ref switchValueVar, switchOnLengthBlock, switchOnLengthBlockStartOffset, out var blocksByLength))
return false;
List<(string, ILInstruction)> stringValues = new();
Expand All @@ -1216,41 +1218,51 @@ private bool MatchRoslynSwitchOnStringUsingLengthAndChar(Block block, int i)
else
{
int length = (int)b.Length.Intervals[0].Start;
if (MatchSwitchOnCharBlock(b.TargetBlock, length, switchValueVar, out var mapping))
switch (b.TargetBlock)
{
foreach (var item in mapping)
{
if (!stringValues.Any(x => x.Item1 == item.StringValue))
case Leave leave:
break;
case Block targetBlock:
if (MatchSwitchOnCharBlock(targetBlock, length, switchValueVar, out var mapping))
{
foreach (var item in mapping)
{
if (!stringValues.Any(x => x.Item1 == item.StringValue))
{
stringValues.Add(item);
}
else
{
return false;
}
}
}
else if (MatchRoslynCaseBlockHead(targetBlock, switchValueVar, out var bodyOrLeave, out var exit, out string stringValue, out _))
{
if (exit != defaultCase)
return false;
if (!stringValues.Any(x => x.Item1 == stringValue))
{
stringValues.Add((stringValue, bodyOrLeave));
}
else
{
return false;
}
}
else if (length == 0)
{
stringValues.Add(item);
stringValues.Add(("", b.TargetBlock));
}
else
{
return false;
}
}
}
else if (MatchRoslynCaseBlockHead(b.TargetBlock, switchValueVar, out var bodyOrLeave, out var exit, out string stringValue, out _))
{
if (exit != defaultCase)
break;
default:
return false;
if (!stringValues.Any(x => x.Item1 == stringValue))
{
stringValues.Add((stringValue, bodyOrLeave));
}
else
{
return false;
}
}
else if (length == 0)
{
stringValues.Add(("", b.TargetBlock));
}
else
{
return false;
}

}
}

Expand Down Expand Up @@ -1278,7 +1290,7 @@ private bool MatchRoslynSwitchOnStringUsingLengthAndChar(Block block, int i)
}
var newSwitch = new SwitchInstruction(new StringToInt(new LdLoc(switchValueVar), values, switchValueVar.Type));
newSwitch.Sections.AddRange(sections);
newSwitch.Sections.Add(new SwitchSection { Labels = defaultLabel, Body = new Branch(defaultCase) });
newSwitch.Sections.Add(new SwitchSection { Labels = defaultLabel, Body = defaultCase is Block b2 ? new Branch(b2) : defaultCase });
newSwitch.AddILRange(instructions[i]);
if (nullCase != null)
{
Expand Down Expand Up @@ -1399,7 +1411,7 @@ bool MatchSwitchOnCharBlock(Block block, int length, ILVariable switchValueVar,
return results?.Count > 0;
}

bool MatchSwitchOnLengthBlock(ref ILVariable switchValueVar, Block switchOnLengthBlock, int startOffset, out List<(LongSet Length, Block TargetBlock)> blocks)
bool MatchSwitchOnLengthBlock(ref ILVariable switchValueVar, Block switchOnLengthBlock, int startOffset, out List<(LongSet Length, ILInstruction TargetBlock)> blocks)
{
blocks = null;
SwitchInstruction @switch;
Expand Down Expand Up @@ -1482,17 +1494,18 @@ bool MatchSwitchOnLengthBlock(ref ILVariable switchValueVar, Block switchOnLengt
{
if (section.HasNullLabel)
return false;
if (!section.Body.MatchBranch(out var target))
if (!section.Body.MatchBranch(out var target) && !section.Body.MatchLeave(out _))
return false;
ILInstruction targetInst = target ?? section.Body;
if (section.Labels.Count() != 1)
{
defaultCase ??= target;
if (defaultCase != target)
defaultCase ??= targetInst;
if (defaultCase != targetInst)
return false;
}
else
{
blocks.Add((section.Labels, target));
blocks.Add((section.Labels, targetInst));
}
}
return true;
Expand All @@ -1506,10 +1519,10 @@ bool MatchSwitchOnLengthBlock(ref ILVariable switchValueVar, Block switchOnLengt
/// br newDefaultBlock
/// }
/// </summary>
private bool IsNullCheckInDefaultBlock(ref Block exitOrDefaultBlock, ILVariable switchVar, out Block nullValueCaseBlock)
private bool IsNullCheckInDefaultBlock(ref ILInstruction exitOrDefault, ILVariable switchVar, out Block nullValueCaseBlock)
{
nullValueCaseBlock = null;
if (exitOrDefaultBlock == null)
if (exitOrDefault is not Block exitOrDefaultBlock)
return false;
if (!exitOrDefaultBlock.Instructions[0].MatchIfInstruction(out var condition, out var thenBranch))
return false;
Expand All @@ -1523,7 +1536,7 @@ private bool IsNullCheckInDefaultBlock(ref Block exitOrDefaultBlock, ILVariable
return false;
if (elseBlock.Parent != exitOrDefaultBlock.Parent)
return false;
exitOrDefaultBlock = elseBlock;
exitOrDefault = elseBlock;
return true;
}

Expand Down

0 comments on commit e1e2f73

Please sign in to comment.