Browse Source

Run IntroduceExitPoints before loop detection, and let loop detection introduce its own exit points.

Warning: this commit creates broken branches that enter blocks.
pull/728/merge
Daniel Grunwald 9 years ago
parent
commit
ed3d4aba9d
  1. 3
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  2. 1
      ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs
  3. 2
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  4. 30
      ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs
  5. 230
      ICSharpCode.Decompiler/IL/ControlFlow/ExitPoints.cs
  6. 127
      ICSharpCode.Decompiler/IL/ControlFlow/IntroduceExitPoints.cs
  7. 41
      ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs
  8. 2
      ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs
  9. 2
      ICSharpCode.Decompiler/IL/Transforms/Stepper.cs
  10. 15
      ICSharpCode.Decompiler/Tests/TestCases/Correctness/ControlFlow.cs
  11. 7
      ILSpy/ILSpyTraceListener.cs
  12. 12
      ILSpy/Languages/ILAstLanguage.cs

3
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -57,6 +57,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -57,6 +57,7 @@ namespace ICSharpCode.Decompiler.CSharp
new SplitVariables(),
new ILInlining(),
new DetectPinnedRegions(), // must run after inlining but before non-critical control flow transforms
new DetectExitPoints(),
new BlockILTransform {
PostOrderTransforms = {
new ExpressionTransforms() // for RemoveDeadVariableInit
@ -67,9 +68,9 @@ namespace ICSharpCode.Decompiler.CSharp @@ -67,9 +68,9 @@ namespace ICSharpCode.Decompiler.CSharp
new RemoveDeadVariableInit(),
new SwitchDetection(),
new LoopDetection(),
new IntroduceExitPoints(),
new BlockILTransform { // per-block transforms
PostOrderTransforms = {
//new UseExitPoints(),
new ConditionDetection(),
// CachedDelegateInitialization must run after ConditionDetection and before/in LoopingBlockTransform.
new CachedDelegateInitialization(),

1
ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs

@ -289,6 +289,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -289,6 +289,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
}
var vds = new VariableDeclarationStatement(type, v.Name, initializer);
vds.Variables.Single().AddAnnotation(new ILVariableResolveResult(p.Key, p.Key.Type));
Debug.Assert(v.InsertionPoint.nextNode.Role == BlockStatement.StatementRole);
v.InsertionPoint.nextNode.Parent.InsertChildBefore(
v.InsertionPoint.nextNode,
vds,

2
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -286,7 +286,7 @@ @@ -286,7 +286,7 @@
<Compile Include="IL\ControlFlow\ConditionDetection.cs" />
<Compile Include="IL\ControlFlow\ControlFlowSimplification.cs" />
<Compile Include="IL\ControlFlow\DetectPinnedRegions.cs" />
<Compile Include="IL\ControlFlow\IntroduceExitPoints.cs" />
<Compile Include="IL\ControlFlow\ExitPoints.cs" />
<Compile Include="IL\ControlFlow\LoopDetection.cs" />
<Compile Include="IL\ControlFlow\SwitchAnalysis.cs" />
<Compile Include="IL\ControlFlow\SwitchDetection.cs" />

30
ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs

@ -112,7 +112,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -112,7 +112,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
&& targetBlock.Instructions[0].MatchIfInstruction(out nestedCondition, out nestedTrueInst))
{
nestedTrueInst = UnpackBlockContainingOnlyBranch(nestedTrueInst);
if (CompatibleExitInstruction(exitInst, nestedTrueInst)) {
if (DetectExitPoints.CompatibleExitInstruction(exitInst, nestedTrueInst)) {
// "if (...) { if (nestedCondition) goto exitPoint; ... } goto exitPoint;"
// -> "if (... && !nestedCondition) { ... } goto exitPoint;"
context.Step("Combine 'if (cond1 && !cond2)' in then-branch", ifInst);
@ -130,7 +130,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -130,7 +130,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
trueExitInst = targetBlock.Instructions.LastOrDefault();
if (CompatibleExitInstruction(exitInst, trueExitInst)) {
if (DetectExitPoints.CompatibleExitInstruction(exitInst, trueExitInst)) {
// "if (...) { ...; goto exitPoint } goto exitPoint;"
// -> "if (...) { ... } goto exitPoint;"
context.Step("Remove redundant 'goto exitPoint;' in then-branch", ifInst);
@ -151,7 +151,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -151,7 +151,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (IsUsableBranchToChild(cfgNode, exitInst)) {
var targetBlock = ((Branch)exitInst).TargetBlock;
var falseExitInst = targetBlock.Instructions.LastOrDefault();
if (CompatibleExitInstruction(trueExitInst, falseExitInst)) {
if (DetectExitPoints.CompatibleExitInstruction(trueExitInst, falseExitInst)) {
// if (...) { ...; goto exitPoint; } goto nextBlock; nextBlock: ...; goto exitPoint;
// -> if (...) { ... } else { ... } goto exitPoint;
context.Step("Inline block as else-branch", ifInst);
@ -246,28 +246,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -246,28 +246,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
&& targetBlock.IncomingEdgeCount == 1 && targetBlock.FinalInstruction.OpCode == OpCode.Nop;
}
internal static bool CompatibleExitInstruction(ILInstruction exit1, ILInstruction exit2)
{
if (exit1 == null || exit2 == null || exit1.OpCode != exit2.OpCode)
return false;
switch (exit1.OpCode) {
case OpCode.Branch:
Branch br1 = (Branch)exit1;
Branch br2 = (Branch)exit2;
return br1.TargetBlock == br2.TargetBlock;
case OpCode.Leave:
Leave leave1 = (Leave)exit1;
Leave leave2 = (Leave)exit2;
return leave1.TargetContainer == leave2.TargetContainer;
case OpCode.Return:
Return ret1 = (Return)exit1;
Return ret2 = (Return)exit2;
return ret1.ReturnValue == null && ret2.ReturnValue == null;
default:
return false;
}
}
private void HandleSwitchInstruction(ControlFlowNode cfgNode, Block block, SwitchInstruction sw, ref ILInstruction exitInst)
{
Debug.Assert(sw.DefaultBody is Nop);
@ -302,7 +280,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -302,7 +280,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
exitInst = sectionBlock.Instructions.Last();
sectionBlock.Instructions.RemoveAt(sectionBlock.Instructions.Count - 1);
block.Instructions.Add(exitInst);
} else if (sectionBlock != null && CompatibleExitInstruction(exitInst, sectionBlock.Instructions.Last())) {
} else if (sectionBlock != null && DetectExitPoints.CompatibleExitInstruction(exitInst, sectionBlock.Instructions.Last())) {
sectionBlock.Instructions.RemoveAt(sectionBlock.Instructions.Count - 1);
}
}

230
ICSharpCode.Decompiler/IL/ControlFlow/ExitPoints.cs

@ -0,0 +1,230 @@ @@ -0,0 +1,230 @@
// Copyright (c) 2014 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics;
using ICSharpCode.Decompiler.IL.Transforms;
namespace ICSharpCode.Decompiler.IL.ControlFlow
{
/// <summary>
/// Detect suitable exit points for BlockContainers.
///
/// An "exit point" is an instruction that causes control flow
/// to leave the container (a branch, leave or value-less return instruction).
///
/// If an "exit point" instruction is placed immediately following a
/// block container, each equivalent exit point within the container
/// can be replaced with a "leave container" instruction.
///
/// This transform performs this replacement: any exit points
/// equivalent to the exit point following the container are
/// replaced with a leave instruction.
/// Additionally, if the container is not yet followed by an exit point,
/// but has room to introduce such an exit point (i.e. iff the container's
/// end point is currently unreachable), we pick one of the non-return
/// exit points within the container, move it to the position following the
/// container, and replace all instances within the container with a leave
/// instruction.
///
/// This makes it easier for the following transforms to construct
/// control flow that falls out of blocks instead of using goto/break statements.
/// </summary>
public class DetectExitPoints : ILVisitor, IILTransform
{
static readonly Nop ExitNotYetDetermined = new Nop();
static readonly Nop NoExit = new Nop();
// The special ReturnExit value (indicating fall-through out of a void method)
// is considered a compatible exit point with all normal return instructions.
static readonly Return ReturnExit = new Return();
/// <summary>
/// Gets the next instruction after <paramref name="inst"/> is executed.
/// Returns NoExit when the next instruction cannot be identified;
/// returns <c>null</c> when the end of a Block is reached (so that we could insert an arbitrary instruction)
/// </summary>
internal static ILInstruction GetExit(ILInstruction inst)
{
SlotInfo slot = inst.SlotInfo;
if (slot == Block.InstructionSlot) {
Block block = (Block)inst.Parent;
return block.Instructions.ElementAtOrDefault(inst.ChildIndex + 1) ?? ExitNotYetDetermined;
} else if (slot == TryInstruction.TryBlockSlot
|| slot == TryCatchHandler.BodySlot
|| slot == TryCatch.HandlerSlot
|| slot == PinnedRegion.BodySlot)
{
return GetExit(inst.Parent);
} else if (slot == ILFunction.BodySlot) {
return ReturnExit;
}
return NoExit;
}
/// <summary>
/// Returns true iff exit1 and exit2 are both exit instructions
/// (branch, leave, or return) and both represent the same exit.
/// </summary>
internal static bool CompatibleExitInstruction(ILInstruction exit1, ILInstruction exit2)
{
if (exit1 == null || exit2 == null || exit1.OpCode != exit2.OpCode)
return false;
switch (exit1.OpCode) {
case OpCode.Branch:
Branch br1 = (Branch)exit1;
Branch br2 = (Branch)exit2;
return br1.TargetBlock == br2.TargetBlock;
case OpCode.Leave:
Leave leave1 = (Leave)exit1;
Leave leave2 = (Leave)exit2;
return leave1.TargetContainer == leave2.TargetContainer;
case OpCode.Return:
Return ret1 = (Return)exit1;
Return ret2 = (Return)exit2;
return ret1.ReturnValue == null && ret2.ReturnValue == null;
default:
return false;
}
}
BlockContainer currentContainer;
/// <summary>
/// The instruction that will be executed next after leaving the currentContainer.
/// <c>null</c> means the container is last in its parent block, and thus does not
/// yet have any leave instructions. This means we can move any exit instruction of
/// our choice our of the container and replace it with a leave instruction.
/// </summary>
ILInstruction currentExit;
public void Run(ILFunction function, ILTransformContext context)
{
currentExit = NoExit;
function.AcceptVisitor(this);
}
protected override void Default(ILInstruction inst)
{
foreach (var child in inst.Children)
child.AcceptVisitor(this);
}
protected internal override void VisitBlockContainer(BlockContainer container)
{
var oldExit = currentExit;
var oldContainer = currentContainer;
var thisExit = GetExit(container);
currentExit = thisExit;
currentContainer = container;
base.VisitBlockContainer(container);
if (thisExit == ExitNotYetDetermined && currentExit != ExitNotYetDetermined) {
// This transform determined an exit point.
Debug.Assert(!currentExit.MatchLeave(currentContainer));
ILInstruction inst = container;
// traverse up to the block (we'll always find one because GetExit
// only returns ExitNotYetDetermined if there's a block)
while (inst.Parent.OpCode != OpCode.Block)
inst = inst.Parent;
Block block = (Block)inst.Parent;
block.Instructions.Add(currentExit);
} else {
Debug.Assert(thisExit == currentExit);
}
currentExit = oldExit;
currentContainer = oldContainer;
}
protected internal override void VisitBlock(Block block)
{
// Don't use foreach loop, because the children might add to the block
for (int i = 0; i < block.Instructions.Count; i++) {
block.Instructions[i].AcceptVisitor(this);
}
}
void HandleExit(ILInstruction inst)
{
if (currentExit == ExitNotYetDetermined && !(inst is Return)) {
currentExit = inst;
inst.ReplaceWith(new Leave(currentContainer) { ILRange = inst.ILRange });
} else if (CompatibleExitInstruction(inst, currentExit)) {
inst.ReplaceWith(new Leave(currentContainer) { ILRange = inst.ILRange });
}
}
protected internal override void VisitBranch(Branch inst)
{
if (!inst.TargetBlock.IsDescendantOf(currentContainer)) {
HandleExit(inst);
}
}
protected internal override void VisitLeave(Leave inst)
{
HandleExit(inst);
}
protected internal override void VisitReturn(Return inst)
{
if (inst.ReturnValue == null) {
// only void returns are considered exit points
HandleExit(inst);
} else {
base.VisitReturn(inst);
}
}
}
/// <summary>
/// Like DetectExitPoints, but only uses existing exit points
/// (by replacing compatible instructions with Leave),
/// without introducing any exit points even if it were possible.
/// </summary>
class UseExitPoints : IBlockTransform
{
BlockContainer currentContainer;
ILInstruction exitPoint;
public void Run(Block block, BlockTransformContext context)
{
currentContainer = context.Container;
exitPoint = DetectExitPoints.GetExit(context.Container);
Visit(block);
}
void Visit(ILInstruction inst)
{
switch (inst.OpCode) {
case OpCode.Return:
case OpCode.Leave:
case OpCode.Branch:
if (DetectExitPoints.CompatibleExitInstruction(inst, exitPoint)) {
inst.ReplaceWith(new Leave(currentContainer) { ILRange = inst.ILRange });
}
break;
case OpCode.Block:
// This is a post-order block transform; skip over nested blocks
// as those are already processed.
return;
}
foreach (var c in inst.Children) {
Visit(c);
}
}
}
}

127
ICSharpCode.Decompiler/IL/ControlFlow/IntroduceExitPoints.cs

@ -1,127 +0,0 @@ @@ -1,127 +0,0 @@
// Copyright (c) 2014 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System.Diagnostics;
using ICSharpCode.Decompiler.IL.Transforms;
namespace ICSharpCode.Decompiler.IL.ControlFlow
{
/// <summary>
/// Control flow transform: use 'leave' instructions instead of 'br' where possible.
/// </summary>
public class IntroduceExitPoints : ILVisitor, IILTransform
{
public void Run(ILFunction function, ILTransformContext context)
{
function.AcceptVisitor(this);
}
static readonly Nop NoExit = new Nop();
static readonly Return ReturnExit = new Return();
protected override void Default(ILInstruction inst)
{
foreach (var child in inst.Children)
child.AcceptVisitor(this);
}
BlockContainer currentContainer;
ILInstruction currentExit = NoExit;
protected internal override void VisitBlockContainer(BlockContainer container)
{
var oldExit = currentExit;
var oldContainer = currentContainer;
var thisExit = GetExit(container);
currentExit = thisExit;
currentContainer = container;
base.VisitBlockContainer(container);
if (thisExit == null && currentExit != null) {
Debug.Assert(!currentExit.MatchLeave(currentContainer));
ILInstruction inst = container;
// traverse up to the block (we'll always find one because GetExit only returns null if there's a block)
while (inst.Parent.OpCode != OpCode.Block)
inst = inst.Parent;
Block block = (Block)inst.Parent;
block.Instructions.Add(currentExit);
} else {
Debug.Assert(thisExit == currentExit);
}
currentExit = oldExit;
currentContainer = oldContainer;
}
/// <summary>
/// Gets the next instruction after <paramref name="inst"/> is executed.
/// Returns NoExit when the next instruction cannot be identified;
/// returns <c>null</c> when the end of a Block is reached (so that we could insert an arbitrary instruction)
/// </summary>
ILInstruction GetExit(ILInstruction inst)
{
SlotInfo slot = inst.SlotInfo;
if (slot == Block.InstructionSlot) {
Block block = (Block)inst.Parent;
return block.Instructions.ElementAtOrDefault(inst.ChildIndex + 1);
} else if (slot == TryInstruction.TryBlockSlot || slot == TryCatchHandler.BodySlot || slot == TryCatch.HandlerSlot || slot == PinnedRegion.BodySlot) {
return GetExit(inst.Parent);
} else if (slot == ILFunction.BodySlot) {
return ReturnExit;
}
return NoExit;
}
protected internal override void VisitBlock(Block block)
{
// Don't use foreach loop, because the children might add to the block
for (int i = 0; i < block.Instructions.Count; i++) {
block.Instructions[i].AcceptVisitor(this);
}
}
void HandleExit(ILInstruction inst)
{
if (currentExit == null) {
currentExit = inst;
inst.ReplaceWith(new Leave(currentContainer) { ILRange = inst.ILRange });
} else if (ConditionDetection.CompatibleExitInstruction(inst, currentExit)) {
inst.ReplaceWith(new Leave(currentContainer) { ILRange = inst.ILRange });
}
}
protected internal override void VisitBranch(Branch inst)
{
if (!inst.TargetBlock.IsDescendantOf(currentContainer)) {
HandleExit(inst);
}
}
protected internal override void VisitLeave(Leave inst)
{
HandleExit(inst);
}
protected internal override void VisitReturn(Return inst)
{
if (inst.ReturnValue == null) {
HandleExit(inst);
} else {
base.VisitReturn(inst);
}
}
}
}

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

@ -153,9 +153,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -153,9 +153,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
}
if (loop != null) {
var headBlock = (Block)h.UserData;
context.Step($"Construct loop with head {headBlock.Label}", headBlock);
// 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);
ExtendLoop(h, loop, out var exitPoint);
// 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)
@ -165,7 +167,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -165,7 +167,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
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");
}
ConstructLoop(loop);
ConstructLoop(loop, exitPoint);
}
// Recurse into the dominator tree to find other possible loop heads
foreach (var child in h.DominatorTreeChildren) {
@ -221,7 +223,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -221,7 +223,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// Observations:
/// * If a node is in-loop, so are all its ancestors in the dominator tree (up to the loop entry point)
/// * If there are no exits reachable from a node (i.e. all paths from that node lead to a return/throw instruction),
/// it is valid to put the dominator tree rooted at that node into either partition independently of
/// it is valid to put the group of nodes dominated by that node into either partition independently of
/// any other nodes except for the ancestors in the dominator tree.
/// (exception: the loop head itself must always be in-loop)
///
@ -255,20 +257,24 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -255,20 +257,24 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
///
/// Precondition: Requires that a node is marked as visited iff it is contained in the loop.
/// </remarks>
void ExtendLoop(ControlFlowNode loopHead, List<ControlFlowNode> loop)
void ExtendLoop(ControlFlowNode loopHead, List<ControlFlowNode> loop, out ControlFlowNode exitPoint)
{
ComputeNodesWithReachableExits();
ControlFlowNode exitPoint = FindExitPoint(loopHead, loop);
exitPoint = FindExitPoint(loopHead, loop);
Debug.Assert(!loop.Contains(exitPoint), "Cannot pick an exit point that is part of the natural loop");
if (exitPoint != null) {
foreach (var node in TreeTraversal.PreOrder(loopHead, n => (n != exitPoint) ? n.DominatorTreeChildren : null)) {
// TODO: did FindExitPoint really not touch the visited flag?
// 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.
var ep = exitPoint;
foreach (var node in TreeTraversal.PreOrder(loopHead, n => (n != ep) ? n.DominatorTreeChildren : null)) {
if (node != exitPoint && !node.Visited) {
loop.Add(node);
}
}
} else {
// TODO: did FindExitPoint really not touch the visited flag?
// We are in case 2, but could not find a suitable exit point.
// Heuristically try to minimize the number of exit points
// (but we'll always end up with more than 1 exit and will require goto statements).
ExtendLoopHeuristic(loopHead, loop, loopHead);
}
}
@ -314,7 +320,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -314,7 +320,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
return false;
}
/// <summary>
/// Finds a suitable single exit point for the specified loop.
/// </summary>
/// <remarks>This method must not write to the Visited flags on the CFG.</remarks>
ControlFlowNode FindExitPoint(ControlFlowNode loopHead, IReadOnlyList<ControlFlowNode> naturalLoop)
{
if (!nodeHasReachableExit[loopHead.UserIndex]) {
@ -365,6 +375,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -365,6 +375,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// over the dominator tree and pick the node with the maximum amount of code.
/// </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>
int PickExitPoint(ControlFlowNode node, ref ControlFlowNode exitPoint, ref int exitPointCodeAmount)
{
int codeAmount = ((Block)node.UserData).Children.Count;
@ -474,10 +485,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -474,10 +485,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// <summary>
/// Move the blocks associated with the loop into a new block container.
/// </summary>
void ConstructLoop(List<ControlFlowNode> loop)
void ConstructLoop(List<ControlFlowNode> loop, ControlFlowNode exitPoint)
{
Block oldEntryPoint = (Block)loop[0].UserData;
Block exitTargetBlock = (Block)exitPoint?.UserData;
BlockContainer loopContainer = new BlockContainer();
Block newEntryPoint = new Block();
loopContainer.Blocks.Add(newEntryPoint);
@ -487,6 +499,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -487,6 +499,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
newEntryPoint.FinalInstruction = oldEntryPoint.FinalInstruction;
newEntryPoint.ILRange = oldEntryPoint.ILRange;
oldEntryPoint.Instructions.ReplaceList(new[] { loopContainer });
if (exitTargetBlock != null)
oldEntryPoint.Instructions.Add(new Branch(exitTargetBlock));
oldEntryPoint.FinalInstruction = new Nop();
// Move other blocks into the loop body: they're all dominated by the loop header,
@ -502,8 +516,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -502,8 +516,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// Rewrite branches within the loop from oldEntryPoint to newEntryPoint:
foreach (var branch in loopContainer.Descendants.OfType<Branch>()) {
if (branch.TargetBlock == oldEntryPoint)
if (branch.TargetBlock == oldEntryPoint) {
branch.TargetBlock = newEntryPoint;
} else if (branch.TargetBlock == exitTargetBlock) {
branch.ReplaceWith(new Leave(loopContainer) { ILRange = branch.ILRange });
}
}
}
}

2
ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs

@ -71,7 +71,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -71,7 +71,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
var blockContext = new BlockTransformContext(context);
blockContext.Function = function;
foreach (var container in function.Descendants.OfType<BlockContainer>()) {
foreach (var container in function.Descendants.OfType<BlockContainer>().ToList()) {
context.CancellationToken.ThrowIfCancellationRequested();
var cfg = LoopDetection.BuildCFG(container);
Dominance.ComputeDominance(cfg[0], context.CancellationToken);

2
ICSharpCode.Decompiler/IL/Transforms/Stepper.cs

@ -94,7 +94,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -94,7 +94,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
private Node StepInternal(string description, ILInstruction near)
{
if (step >= StepLimit) {
if (step == StepLimit) {
if (IsDebug)
Debugger.Break();
else

15
ICSharpCode.Decompiler/Tests/TestCases/Correctness/ControlFlow.cs

@ -87,4 +87,19 @@ class ControlFlow @@ -87,4 +87,19 @@ class ControlFlow
Console.WriteLine("else");
}
}
int Dim2Search(int arg)
{
var tens = new[] { 10, 20, 30 };
var ones = new[] { 1, 2, 3 };
for (int i = 0; i < tens.Length; i++) {
for (int j = 0; j < ones.Length; j++) {
if (tens[i] + ones[j] == arg)
return i;
}
}
return -1;
}
}

7
ILSpy/ILSpyTraceListener.cs

@ -70,7 +70,7 @@ namespace ICSharpCode.ILSpy @@ -70,7 +70,7 @@ namespace ICSharpCode.ILSpy
thread.Start();
thread.Join();
if (result == 0) { // throw
throw new Exception(message);
throw new AssertionFailedException(message);
} else if (result == 1) { // debug
Debugger.Break();
} else if (result == 2) { // ignore
@ -96,4 +96,9 @@ namespace ICSharpCode.ILSpy @@ -96,4 +96,9 @@ namespace ICSharpCode.ILSpy
}
}
}
class AssertionFailedException : Exception
{
public AssertionFailedException(string message) : base(message) { }
}
}

12
ILSpy/Languages/ILAstLanguage.cs

@ -161,14 +161,14 @@ namespace ICSharpCode.ILSpy @@ -161,14 +161,14 @@ namespace ICSharpCode.ILSpy
try {
il.RunTransforms(transforms, context);
} catch (StepLimitReachedException) {
il.WriteTo(output);
return;
} finally {
// update stepper even if a transform crashed unexpectedly
if (options.StepLimit == int.MaxValue) {
Stepper = context.Stepper;
OnStepperUpdated(new EventArgs());
}
}
il.WriteTo(output);
if (options.StepLimit == int.MaxValue) {
Stepper = context.Stepper;
OnStepperUpdated(new EventArgs());
}
}
}
}

Loading…
Cancel
Save