Browse Source

First attempt at loop extension.

pull/728/head
Daniel Grunwald 10 years ago
parent
commit
d6b9d39f38
  1. 89
      ICSharpCode.Decompiler/IL/Transforms/LoopDetection.cs
  2. 5
      ICSharpCode.Decompiler/Util/CollectionExtensions.cs

89
ICSharpCode.Decompiler/IL/Transforms/LoopDetection.cs

@ -19,6 +19,7 @@ using System; @@ -19,6 +19,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.NRefactory.Utils;
using ICSharpCode.Decompiler.FlowAnalysis;
namespace ICSharpCode.Decompiler.IL.Transforms
@ -123,10 +124,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -123,10 +124,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
if (loop != null) {
// loop now is the union of all natural loops with loop head h.
// Try to extend the loop to reduce the number of exit points:
ExtendLoop(h, loop, h);
// Sort blocks in the loop in reverse post-order to make the output look a bit nicer.
// (if the loop doesn't contain nested loops, this is a topological sort)
loop.Sort((a, b) => b.PostOrderNumber.CompareTo(a.PostOrderNumber));
Debug.Assert(loop[0] == h); // TODO: is this guaranteed after sorting?
Debug.Assert(loop[0] == h);
foreach (var node in loop) {
node.Visited = false; // reset visited flag so that we can find nested loops
Debug.Assert(h.Dominates(node), "The loop body must be dominated by the loop head");
@ -139,6 +143,89 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -139,6 +143,89 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
/// <summary>
/// Given a natural loop, add additional CFG nodes to the loop in order
/// to reduce the number of exit points out of the loop.
/// We do this because C# only allows reaching a single exit point (with 'break'
/// statements or when the loop condition evaluates to false), so we'd have
/// to introduce 'goto' statements for any additional exit points.
///
/// Definition: a loop exit point is a CFG node that is not itself part of the loop,
/// but has at least one predecessor which is part of the loop.
///
/// Nodes can only be added to the loop if they are dominated by the loop head.
/// When adding a node to the loop, we implicitly also add all of that node's predecessors
/// to the loop. (this ensures that the loop keeps its single entry point)
///
/// Adding a node to the loop has two effects on the the number of exit points:
/// * exit points that were added to the loop are no longer exit points, thus reducing the total number of exit points
/// * successors of the newly added nodes might be new, additional exit points
///
/// The loop extension algorithm proceeds traverses the loop head's dominator tree in pre-order.
/// For each candidate node, we detect whether adding it to the loop reduces the number of exit points.
/// If it does, the candidate is added to the loop.
/// </summary>
/// <remarks>
/// Requires and maintains the invariant that a node is marked as visited iff it is contained in the loop.
///
/// Note: I don't think this works reliably to minimize the number of exit points,
/// it's just a heuristic that should reduce the number of exit points in most cases.
/// I think what we're really looking for is a minimum vertex cut of the following flow graph:
/// * all nodes that are part of the natural loop are combined into a single node (the source node)
/// * all control flow nodes that are dominated by the loop head (but not part of the loop)
/// are nodes in the graph
/// * all nodes that in the loop's dominance frontier are nodes in the graph
/// * connections are as usual in the CFG
/// * the nodes in the loop's dominance frontier are additionally connected to the sink node.
///
/// Also, if the only way to leave the loop is through 'ret' or 'leave' instructions, or 'br' instructions
/// that leave the block container, this method has the effect of adding more code than necessary to the loop,
/// as those instructions do not have corresponding control flow edges.
/// </remarks>
void ExtendLoop(ControlFlowNode loopHead, List<ControlFlowNode> loop, ControlFlowNode candidate)
{
Debug.Assert(candidate.Visited == loop.Contains(candidate));
if (!candidate.Visited) {
// This node not yet part of the loop, but might be added
List<ControlFlowNode> additionalNodes = new List<ControlFlowNode>();
// Find additionalNodes nodes and mark them as visited.
candidate.TraversePreOrder(n => n.Predecessors, additionalNodes.Add);
// This means Visited now represents the candiate extended loop.
// Determine new exit points that are reachable from the additional nodes
// (note: some of these might have previously been exit points, too)
var newExitPoints = additionalNodes.SelectMany(n => n.Successors).Where(n => !n.Visited).ToHashSet();
// Make visited represent the unextended loop, so that we can measure the exit points
// in the old state.
foreach (var node in additionalNodes)
node.Visited = false;
// Measure number of added and removed exit points
int removedExitPoints = additionalNodes.Count(IsExitPoint);
int addedExitPoints = newExitPoints.Count(n => !IsExitPoint(n));
if (removedExitPoints > addedExitPoints) {
// We can reduce the number of exit points by adding the candidate node to the loop.
candidate.TraversePreOrder(n => n.Predecessors, loop.Add);
}
}
// Pre-order traversal of dominator tree
foreach (var node in candidate.DominatorTreeChildren) {
ExtendLoop(loopHead, loop, node);
}
}
/// <summary>
/// Gets whether 'node' is an exit point for the loop marked by the Visited flag.
/// </summary>
bool IsExitPoint(ControlFlowNode node)
{
if (node.Visited)
return false; // nodes in the loop are not exit points
foreach (var pred in node.Predecessors) {
if (pred.Visited)
return true;
}
return false;
}
/// <summary>
/// Move the blocks associated with the loop into a new block container.
/// </summary>

5
ICSharpCode.Decompiler/Util/CollectionExtensions.cs

@ -8,6 +8,11 @@ namespace ICSharpCode.Decompiler @@ -8,6 +8,11 @@ namespace ICSharpCode.Decompiler
{
static class CollectionExtensions
{
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> input)
{
return new HashSet<T>(input);
}
public static T PopOrDefault<T>(this Stack<T> stack)
{
if (stack.Count == 0)

Loading…
Cancel
Save