Browse Source

Select outer-loop continue branches as break targets for switches in nested loops

pull/1258/head
Chicken-Bones 8 years ago
parent
commit
d8244e347b
  1. 24
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.cs
  2. 87
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.il
  3. 69
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.opt.il
  4. 76
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.opt.roslyn.il
  5. 99
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.roslyn.il
  6. 68
      ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs
  7. 155
      ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs

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

@ -832,6 +832,30 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -832,6 +832,30 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
Console.WriteLine("end");
}
public static void SwitchWithContinueInDoubleLoop()
{
bool value = false;
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
switch (i + j) {
case 1:
case 3:
case 5:
case 7:
case 11:
case 13:
case 17:
break;
default:
continue;
}
value = true;
break;
}
}
Console.WriteLine(value);
}
public static void SwitchLoopNesting()
{
for (int i = 0; i < 10; i++) {

87
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.il

@ -2674,6 +2674,93 @@ @@ -2674,6 +2674,93 @@
IL_0050: ret
} // end of method Switch::SwitchWithContinue7
.method public hidebysig static void SwitchWithContinueInDoubleLoop() cil managed
{
// Code size 128 (0x80)
.maxstack 2
.locals init (bool V_0,
int32 V_1,
int32 V_2,
int32 V_3,
bool V_4)
IL_0000: nop
IL_0001: ldc.i4.0
IL_0002: stloc.0
IL_0003: ldc.i4.0
IL_0004: stloc.1
IL_0005: br.s IL_006d
IL_0007: nop
IL_0008: ldc.i4.0
IL_0009: stloc.2
IL_000a: br.s IL_005d
IL_000c: nop
IL_000d: ldloc.1
IL_000e: ldloc.2
IL_000f: add
IL_0010: stloc.3
IL_0011: ldloc.3
IL_0012: ldc.i4.1
IL_0013: sub
IL_0014: switch (
IL_0051,
IL_0053,
IL_0051,
IL_0053,
IL_0051,
IL_0053,
IL_0051)
IL_0035: ldloc.3
IL_0036: ldc.i4.s 11
IL_0038: sub
IL_0039: switch (
IL_0051,
IL_0053,
IL_0051)
IL_004a: ldloc.3
IL_004b: ldc.i4.s 17
IL_004d: beq.s IL_0051
IL_004f: br.s IL_0053
IL_0051: br.s IL_0055
IL_0053: br.s IL_0059
IL_0055: ldc.i4.1
IL_0056: stloc.0
IL_0057: br.s IL_0068
IL_0059: ldloc.2
IL_005a: ldc.i4.1
IL_005b: add
IL_005c: stloc.2
IL_005d: ldloc.2
IL_005e: ldc.i4.s 10
IL_0060: clt
IL_0062: stloc.s V_4
IL_0064: ldloc.s V_4
IL_0066: brtrue.s IL_000c
IL_0068: nop
IL_0069: ldloc.1
IL_006a: ldc.i4.1
IL_006b: add
IL_006c: stloc.1
IL_006d: ldloc.1
IL_006e: ldc.i4.s 10
IL_0070: clt
IL_0072: stloc.s V_4
IL_0074: ldloc.s V_4
IL_0076: brtrue.s IL_0007
IL_0078: ldloc.0
IL_0079: call void [mscorlib]System.Console::WriteLine(bool)
IL_007e: nop
IL_007f: ret
} // end of method Switch::SwitchWithContinueInDoubleLoop
.method public hidebysig static void SwitchLoopNesting() cil managed
{
// Code size 153 (0x99)

69
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.opt.il

@ -2146,6 +2146,75 @@ @@ -2146,6 +2146,75 @@
IL_003c: ret
} // end of method Switch::SwitchWithContinue7
.method public hidebysig static void SwitchWithContinueInDoubleLoop() cil managed
{
// Code size 105 (0x69)
.maxstack 2
.locals init (bool V_0,
int32 V_1,
int32 V_2,
int32 V_3)
IL_0000: ldc.i4.0
IL_0001: stloc.0
IL_0002: ldc.i4.0
IL_0003: stloc.1
IL_0004: br.s IL_005d
IL_0006: ldc.i4.0
IL_0007: stloc.2
IL_0008: br.s IL_0054
IL_000a: ldloc.1
IL_000b: ldloc.2
IL_000c: add
IL_000d: stloc.3
IL_000e: ldloc.3
IL_000f: ldc.i4.1
IL_0010: sub
IL_0011: switch (
IL_004c,
IL_0050,
IL_004c,
IL_0050,
IL_004c,
IL_0050,
IL_004c)
IL_0032: ldloc.3
IL_0033: ldc.i4.s 11
IL_0035: sub
IL_0036: switch (
IL_004c,
IL_0050,
IL_004c)
IL_0047: ldloc.3
IL_0048: ldc.i4.s 17
IL_004a: bne.un.s IL_0050
IL_004c: ldc.i4.1
IL_004d: stloc.0
IL_004e: br.s IL_0059
IL_0050: ldloc.2
IL_0051: ldc.i4.1
IL_0052: add
IL_0053: stloc.2
IL_0054: ldloc.2
IL_0055: ldc.i4.s 10
IL_0057: blt.s IL_000a
IL_0059: ldloc.1
IL_005a: ldc.i4.1
IL_005b: add
IL_005c: stloc.1
IL_005d: ldloc.1
IL_005e: ldc.i4.s 10
IL_0060: blt.s IL_0006
IL_0062: ldloc.0
IL_0063: call void [mscorlib]System.Console::WriteLine(bool)
IL_0068: ret
} // end of method Switch::SwitchWithContinueInDoubleLoop
.method public hidebysig static void SwitchLoopNesting() cil managed
{
// Code size 101 (0x65)

76
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.opt.roslyn.il

@ -2305,6 +2305,82 @@ @@ -2305,6 +2305,82 @@
IL_0033: ret
} // end of method Switch::SwitchWithContinue7
.method public hidebysig static void SwitchWithContinueInDoubleLoop() cil managed
{
// Code size 101 (0x65)
.maxstack 2
.locals init (bool V_0,
int32 V_1,
int32 V_2,
int32 V_3)
IL_0000: ldc.i4.0
IL_0001: stloc.0
IL_0002: ldc.i4.0
IL_0003: stloc.1
IL_0004: br.s IL_0059
IL_0006: ldc.i4.0
IL_0007: stloc.2
IL_0008: br.s IL_0050
IL_000a: ldloc.1
IL_000b: ldloc.2
IL_000c: add
IL_000d: stloc.3
IL_000e: ldloc.3
IL_000f: ldc.i4.s 11
IL_0011: bgt.s IL_003e
IL_0013: ldloc.3
IL_0014: ldc.i4.1
IL_0015: sub
IL_0016: switch (
IL_0048,
IL_004c,
IL_0048,
IL_004c,
IL_0048,
IL_004c,
IL_0048)
IL_0037: ldloc.3
IL_0038: ldc.i4.s 11
IL_003a: beq.s IL_0048
IL_003c: br.s IL_004c
IL_003e: ldloc.3
IL_003f: ldc.i4.s 13
IL_0041: beq.s IL_0048
IL_0043: ldloc.3
IL_0044: ldc.i4.s 17
IL_0046: bne.un.s IL_004c
IL_0048: ldc.i4.1
IL_0049: stloc.0
IL_004a: br.s IL_0055
IL_004c: ldloc.2
IL_004d: ldc.i4.1
IL_004e: add
IL_004f: stloc.2
IL_0050: ldloc.2
IL_0051: ldc.i4.s 10
IL_0053: blt.s IL_000a
IL_0055: ldloc.1
IL_0056: ldc.i4.1
IL_0057: add
IL_0058: stloc.1
IL_0059: ldloc.1
IL_005a: ldc.i4.s 10
IL_005c: blt.s IL_0006
IL_005e: ldloc.0
IL_005f: call void [mscorlib]System.Console::WriteLine(bool)
IL_0064: ret
} // end of method Switch::SwitchWithContinueInDoubleLoop
.method public hidebysig static void SwitchLoopNesting() cil managed
{
// Code size 92 (0x5c)

99
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.roslyn.il

@ -3019,6 +3019,105 @@ @@ -3019,6 +3019,105 @@
IL_004b: ret
} // end of method Switch::SwitchWithContinue7
.method public hidebysig static void SwitchWithContinueInDoubleLoop() cil managed
{
// Code size 128 (0x80)
.maxstack 2
.locals init (bool V_0,
int32 V_1,
int32 V_2,
int32 V_3,
bool V_4,
bool V_5)
IL_0000: nop
IL_0001: ldc.i4.0
IL_0002: stloc.0
IL_0003: ldc.i4.0
IL_0004: stloc.1
IL_0005: br.s IL_006d
IL_0007: nop
IL_0008: ldc.i4.0
IL_0009: stloc.2
IL_000a: br.s IL_005d
IL_000c: nop
IL_000d: ldloc.1
IL_000e: ldloc.2
IL_000f: add
IL_0010: stloc.3
IL_0011: ldloc.3
IL_0012: ldc.i4.s 11
IL_0014: bgt.s IL_0043
IL_0016: ldloc.3
IL_0017: ldc.i4.1
IL_0018: sub
IL_0019: switch (
IL_0051,
IL_0053,
IL_0051,
IL_0053,
IL_0051,
IL_0053,
IL_0051)
IL_003a: br.s IL_003c
IL_003c: ldloc.3
IL_003d: ldc.i4.s 11
IL_003f: beq.s IL_0051
IL_0041: br.s IL_0053
IL_0043: ldloc.3
IL_0044: ldc.i4.s 13
IL_0046: beq.s IL_0051
IL_0048: br.s IL_004a
IL_004a: ldloc.3
IL_004b: ldc.i4.s 17
IL_004d: beq.s IL_0051
IL_004f: br.s IL_0053
IL_0051: br.s IL_0055
IL_0053: br.s IL_0059
IL_0055: ldc.i4.1
IL_0056: stloc.0
IL_0057: br.s IL_0068
IL_0059: ldloc.2
IL_005a: ldc.i4.1
IL_005b: add
IL_005c: stloc.2
IL_005d: ldloc.2
IL_005e: ldc.i4.s 10
IL_0060: clt
IL_0062: stloc.s V_4
IL_0064: ldloc.s V_4
IL_0066: brtrue.s IL_000c
IL_0068: nop
IL_0069: ldloc.1
IL_006a: ldc.i4.1
IL_006b: add
IL_006c: stloc.1
IL_006d: ldloc.1
IL_006e: ldc.i4.s 10
IL_0070: clt
IL_0072: stloc.s V_5
IL_0074: ldloc.s V_5
IL_0076: brtrue.s IL_0007
IL_0078: ldloc.0
IL_0079: call void [mscorlib]System.Console::WriteLine(bool)
IL_007e: nop
IL_007f: ret
} // end of method Switch::SwitchWithContinueInDoubleLoop
.method public hidebysig static void SwitchLoopNesting() cil managed
{
// Code size 140 (0x8c)

68
ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs

@ -42,6 +42,15 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -42,6 +42,15 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// <summary>Block container corresponding to the current cfg.</summary>
BlockContainer currentBlockContainer;
/// <summary>
/// Enabled during DetectSwitchBody, used by ExtendLoop and children
/// </summary>
private bool isSwitch;
/// <summary>
/// Used when isSwitch == true, to determine appropriate exit points within loops
/// </summary>
private SwitchDetection.LoopContext loopContext;
/// <summary>
/// Check whether 'block' is a loop head; and construct a loop instruction
@ -234,16 +243,16 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -234,16 +243,16 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
///
/// Requires and maintains the invariant that a node is marked as visited iff it is contained in the loop.
/// </remarks>
void ExtendLoop(ControlFlowNode loopHead, List<ControlFlowNode> loop, out ControlFlowNode exitPoint, bool isSwitch=false)
void ExtendLoop(ControlFlowNode loopHead, List<ControlFlowNode> loop, out ControlFlowNode exitPoint)
{
exitPoint = FindExitPoint(loopHead, loop, isSwitch);
exitPoint = FindExitPoint(loopHead, loop);
Debug.Assert(!loop.Contains(exitPoint), "Cannot pick an exit point that is part of the natural loop");
if (exitPoint != null) {
// Either we are in case 1 and just picked an exit that maximizes the amount of code
// outside the loop, or we are in case 2 and found an exit point via post-dominance.
// Note that if exitPoint == NoExitPoint, we end up adding all dominated blocks to the loop.
var ep = exitPoint;
foreach (var node in TreeTraversal.PreOrder(loopHead, n => DominatorTreeChildren(n, ep, isSwitch))) {
foreach (var node in TreeTraversal.PreOrder(loopHead, n => DominatorTreeChildren(n, ep))) {
if (!node.Visited) {
node.Visited = true;
loop.Add(node);
@ -272,9 +281,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -272,9 +281,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// 3) otherwise (exit point unknown, heuristically extend loop): null
/// </returns>
/// <remarks>This method must not write to the Visited flags on the CFG.</remarks>
internal ControlFlowNode FindExitPoint(ControlFlowNode loopHead, IReadOnlyList<ControlFlowNode> naturalLoop, bool isSwitch)
internal ControlFlowNode FindExitPoint(ControlFlowNode loopHead, IReadOnlyList<ControlFlowNode> naturalLoop)
{
bool hasReachableExit = HasReachableExit(loopHead, isSwitch);
bool hasReachableExit = HasReachableExit(loopHead);
if (!hasReachableExit) {
// Case 1:
// There are no nodes n so that loopHead dominates a predecessor of n but not n itself
@ -292,7 +301,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -292,7 +301,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
ControlFlowNode exitPoint = null;
int exitPointILOffset = -1;
foreach (var node in loopHead.DominatorTreeChildren) {
PickExitPoint(node, isSwitch, ref exitPoint, ref exitPointILOffset);
PickExitPoint(node, ref exitPoint, ref exitPointILOffset);
}
return exitPoint;
} else {
@ -300,7 +309,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -300,7 +309,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// We need to pick our exit point so that all paths from the loop head
// to the reachable exits run through that exit point.
var cfg = context.ControlFlowGraph.cfg;
var revCfg = PrepareReverseCFG(loopHead, isSwitch, out int exitNodeArity);
var revCfg = PrepareReverseCFG(loopHead, out int exitNodeArity);
//ControlFlowNode.ExportGraph(cfg).Show("cfg");
//ControlFlowNode.ExportGraph(revCfg).Show("rev");
ControlFlowNode commonAncestor = revCfg[loopHead.UserIndex];
@ -336,8 +345,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -336,8 +345,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (exitNodeArity > 1)
return null;
// Exit node is on the very edge of the tree, and isn't important for determining inclusion
// Still necessary for switch detection to insert correct leave statements
if (exitNodeArity == 1 && isSwitch)
return SwitchDetection.GetBreakTargets(loopHead, loopHead).Distinct().Single();
return loopContext.GetBreakTargets(loopHead).Distinct().Single();
// If exitNodeArity == 0, we should maybe look test if our exits out of the block container are all compatible?
// but I don't think it hurts to have a bit too much code inside the loop in this rare case.
@ -382,12 +393,21 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -382,12 +393,21 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
}
bool HasReachableExit(ControlFlowNode node, bool isSwitch) => isSwitch
? SwitchDetection.GetBreakTargets(context.ControlFlowNode, node).Any()
/// <summary>
/// Extension of ControlFlowGraph.HasReachableExit
/// Uses loopContext.GetBreakTargets().Any() when analyzing switches to avoid
/// classifying continue blocks as reachable exits.
/// </summary>
bool HasReachableExit(ControlFlowNode node) => isSwitch
? loopContext.GetBreakTargets(node).Any()
: context.ControlFlowGraph.HasReachableExit(node);
IEnumerable<ControlFlowNode> DominatorTreeChildren(ControlFlowNode n, ControlFlowNode exitPoint, bool isSwitch) =>
n.DominatorTreeChildren.Where(c => c != exitPoint && (!isSwitch || !SwitchDetection.IsContinue(context.ControlFlowNode, c)));
/// <summary>
/// Returns the children in a loop dominator tree, with an optional exit point
/// Avoids returning continue statements when analysing switches (because increment blocks can be dominated)
/// </summary>
IEnumerable<ControlFlowNode> DominatorTreeChildren(ControlFlowNode n, ControlFlowNode exitPoint) =>
n.DominatorTreeChildren.Where(c => c != exitPoint && (!isSwitch || !loopContext.MatchContinue(c)));
/// <summary>
/// Pick exit point by picking any node that has no reachable exits.
@ -399,14 +419,14 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -399,14 +419,14 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// </summary>
/// <returns>Code amount in <paramref name="node"/> and its dominated nodes.</returns>
/// <remarks>This method must not write to the Visited flags on the CFG.</remarks>
void PickExitPoint(ControlFlowNode node, bool isSwitch, ref ControlFlowNode exitPoint, ref int exitPointILOffset)
void PickExitPoint(ControlFlowNode node, ref ControlFlowNode exitPoint, ref int exitPointILOffset)
{
if (isSwitch && SwitchDetection.IsContinue(context.ControlFlowNode, node))
if (isSwitch && loopContext.MatchContinue(node))
return;
Block block = (Block)node.UserData;
if (block.ILRange.Start > exitPointILOffset
&& !HasReachableExit(node, isSwitch)
&& !HasReachableExit(node)
&& ((Block)node.UserData).Parent == currentBlockContainer)
{
// HasReachableExit(node) == false
@ -425,7 +445,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -425,7 +445,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// moving almost all of the code into the loop.
}
foreach (var child in node.DominatorTreeChildren) {
PickExitPoint(child, isSwitch, ref exitPoint, ref exitPointILOffset);
PickExitPoint(child, ref exitPoint, ref exitPointILOffset);
}
}
@ -446,7 +466,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -446,7 +466,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// 2 = more than one CFG node (not dominated by loopHead) was used as an exit node.
/// </param>
/// <returns></returns>
ControlFlowNode[] PrepareReverseCFG(ControlFlowNode loopHead, bool isSwitch, out int exitNodeArity)
ControlFlowNode[] PrepareReverseCFG(ControlFlowNode loopHead, out int exitNodeArity)
{
ControlFlowNode[] cfg = context.ControlFlowGraph.cfg;
ControlFlowNode[] rev = new ControlFlowNode[cfg.Length + 1];
@ -458,12 +478,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -458,12 +478,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
ControlFlowNode exitNode = new ControlFlowNode { UserIndex = -1 };
rev[cfg.Length] = exitNode;
for (int i = 0; i < cfg.Length; i++) {
if (!loopHead.Dominates(cfg[i]) || isSwitch && SwitchDetection.IsContinue(loopHead, cfg[i]))
if (!loopHead.Dominates(cfg[i]) || isSwitch && cfg[i] != loopHead && loopContext.MatchContinue(cfg[i]))
continue;
// Add reverse edges for all edges in cfg
foreach (var succ in cfg[i].Successors) {
if (isSwitch && SwitchDetection.IsContinue(loopHead, succ))
// edges to outer loops still count as exits (labelled continue not implemented)
if (isSwitch && loopContext.MatchContinue(succ, 1))
continue;
if (loopHead.Dominates(succ)) {
@ -669,11 +690,14 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -669,11 +690,14 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
Debug.Assert(h.UserData == block);
Debug.Assert(!TreeTraversal.PreOrder(h, n => n.DominatorTreeChildren).Any(n => n.Visited));
isSwitch = true;
loopContext = new SwitchDetection.LoopContext(context.ControlFlowGraph, h);
var nodesInSwitch = new List<ControlFlowNode>();
nodesInSwitch.Add(h);
h.Visited = true;
ExtendLoop(h, nodesInSwitch, out var exitPoint, isSwitch: true);
if (exitPoint != null && exitPoint.Predecessors.Count == 1 && !HasReachableExit(exitPoint, true)) {
ExtendLoop(h, nodesInSwitch, out var exitPoint);
if (exitPoint != null && h.Dominates(exitPoint) && exitPoint.Predecessors.Count == 1 && !HasReachableExit(exitPoint)) {
// If the exit point is reachable from just one single "break;",
// it's better to move the code into the switch.
// (unlike loops which should not be nested unless necessary,
@ -716,6 +740,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -716,6 +740,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
branch.ReplaceWith(new Leave(switchContainer) { ILRange = branch.ILRange });
}
}
isSwitch = false;
}
}
}

155
ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs

@ -36,11 +36,99 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -36,11 +36,99 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// </summary>
class SwitchDetection : IILTransform
{
private readonly SwitchAnalysis analysis = new SwitchAnalysis();
private ILTransformContext context;
private BlockContainer currentContainer;
private ControlFlowGraph controlFlowGraph;
private LoopContext loopContext;
/// <summary>
/// When detecting a switch, it is important to distinguish Branch instructions which will
/// eventually decompile to continue; statements.
///
/// A LoopContext is constructed for a node and its dominator tree, as for a Branch to be a continue;
/// statement, it must be contained within the target-loop
///
/// This class also supplies the depth of the loop targetted by a continue; statement relative to the
/// context node, to avoid (or eventually support) labelled continues to outer loops
/// </summary>
public class LoopContext
{
private readonly IDictionary<ControlFlowNode, int> continueDepth = new Dictionary<ControlFlowNode, int>();
public LoopContext(ControlFlowGraph cfg, ControlFlowNode contextNode)
{
var loopHeads = new List<ControlFlowNode>();
void Analyze(ControlFlowNode n)
{
if (n.Visited)
return;
n.Visited = true;
if (n.Dominates(contextNode))
loopHeads.Add(n);
else
n.Successors.ForEach(Analyze);
}
contextNode.Successors.ForEach(Analyze);
foreach (var node in cfg.cfg)
node.Visited = false;
int l = 1;
foreach (var loopHead in loopHeads.OrderBy(n => n.PostOrderNumber))
continueDepth[FindContinue(loopHead)] = l++;
}
private static ControlFlowNode FindContinue(ControlFlowNode loopHead)
{
// potential continue target
var pred = loopHead.Predecessors.OnlyOrDefault(p => p != loopHead && loopHead.Dominates(p));
if (pred == null)
return loopHead;
// match for loop increment block
if (pred.Successors.Count == 1) {
if (HighLevelLoopTransform.MatchIncrementBlock((Block)pred.UserData, out var target) &&target == loopHead.UserData)
return pred;
}
// match do-while condition
if (pred.Successors.Count <= 2) {
if (HighLevelLoopTransform.MatchDoWhileConditionBlock((Block)pred.UserData, out var t1, out var t2) &&
(t1 == loopHead.UserData || t2 == loopHead.UserData))
return pred;
}
return loopHead;
}
public bool MatchContinue(ControlFlowNode node) => MatchContinue(node, out var _);
public bool MatchContinue(ControlFlowNode node, int depth) =>
MatchContinue(node, out int _depth) && depth == _depth;
public bool MatchContinue(ControlFlowNode node, out int depth) => continueDepth.TryGetValue(node, out depth);
SwitchAnalysis analysis = new SwitchAnalysis();
public int GetContinueDepth(ControlFlowNode node) => MatchContinue(node, out var depth) ? depth : 0;
/// <summary>
/// Lists all potential targets for break; statements from a domination tree,
/// assuming the domination tree must be exited via either break; or continue;
///
/// First list all nodes in the dominator tree (excluding continue nodes)
/// Then return the all successors not contained within said tree.
///
/// Note that node will be returned once for each outgoing edge.
/// Labelled continue statements (depth > 1) are counted as break targets
/// </summary>
internal IEnumerable<ControlFlowNode> GetBreakTargets(ControlFlowNode dominator) =>
TreeTraversal.PreOrder(dominator, n => n.DominatorTreeChildren.Where(c => !MatchContinue(c)))
.SelectMany(n => n.Successors)
.Where(n => !dominator.Dominates(n) && !MatchContinue(n, 1));
}
public void Run(ILFunction function, ILTransformContext context)
{
@ -248,8 +336,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -248,8 +336,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
{
if (controlFlowGraph == null)
controlFlowGraph = new ControlFlowGraph(currentContainer, context.CancellationToken);
var switchHead = controlFlowGraph.GetNode(analysis.RootBlock);
loopContext = new LoopContext(controlFlowGraph, switchHead);
// grab the control flow nodes for blocks targetted by each section
var caseNodes = new List<ControlFlowNode>();
foreach (var s in analysis.Sections) {
@ -257,7 +347,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -257,7 +347,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
continue;
var node = controlFlowGraph.GetNode(block);
if (!IsContinue(switchHead, node))
if (!loopContext.MatchContinue(node))
caseNodes.Add(node);
}
@ -274,7 +364,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -274,7 +364,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
return true; // cannot have more than one break case without gotos
// check that case nodes flow through a single point
var breakTargets = caseNodes.Except(externalCases).SelectMany(n => GetBreakTargets(switchHead, n)).ToHashSet();
var breakTargets = caseNodes.Except(externalCases).SelectMany(n => loopContext.GetBreakTargets(n)).ToHashSet();
// if there are multiple break targets, then gotos are required
// if there are none, then the external case (if any) can be the break target
@ -318,62 +408,5 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -318,62 +408,5 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
//add the null case logic to the incoming flow blocks
flowBlocks.Add(nullableBlock);
}
internal static bool IsContinue(ControlFlowNode innerLoopHead, ControlFlowNode node) =>
IsContinue(node, out var outerLoopHead) && outerLoopHead.Dominates(innerLoopHead);
private static bool IsContinue(ControlFlowNode node, out ControlFlowNode loopHead)
{
bool IsLoopHead(ControlFlowNode n) => n.Predecessors.Any(n.Dominates);
ControlFlowNode OnlyInloopPred(ControlFlowNode n) => n.Predecessors.OnlyOrDefault(p => p != n && n.Dominates(p));
loopHead = null;
// loop head
if (IsLoopHead(node)) {
var preBlock = OnlyInloopPred(node);
if (preBlock != null && IsContinue(preBlock, out var preHead) && preHead == node)
return false;
loopHead = node;
return true;
}
// match for loop increment block
if (node.Successors.Count == 1) {
// potential loop head
loopHead = node.Successors.SingleOrDefault(s => IsLoopHead(s) && OnlyInloopPred(s) == node);
if (loopHead != null &&
HighLevelLoopTransform.MatchIncrementBlock((Block)node.UserData, out var target) &&
target == loopHead.UserData)
return true;
}
// match do-while condition
if (node.Successors.Count <= 2) {
// potential loop head
loopHead = node.Successors.OnlyOrDefault(s => IsLoopHead(s) && OnlyInloopPred(s) == node);
if (loopHead != null &&
HighLevelLoopTransform.MatchDoWhileConditionBlock((Block)node.UserData, out var t1, out var t2) &&
(t1 == loopHead.UserData || t2 == loopHead.UserData))
return true;
}
return false;
}
/// <summary>
/// Lists all potential targets for break; statements from a domination tree,
/// assuming the domination tree must be exited via either break; or continue;
///
/// First list all nodes in the dominator tree (excluding continue nodes)
/// Then return the all successors not contained within said tree.
///
/// Note that node will be returned once for every outgoing edge
/// </summary>
internal static IEnumerable<ControlFlowNode> GetBreakTargets(ControlFlowNode loopHead, ControlFlowNode dominator) =>
TreeTraversal.PreOrder(dominator, n => n.DominatorTreeChildren.Where(c => !IsContinue(loopHead, c)))
.SelectMany(n => n.Successors)
.Where(n => !dominator.Dominates(n) && !IsContinue(loopHead, n));
}
}

Loading…
Cancel
Save