mirror of https://github.com/icsharpcode/ILSpy.git
12 changed files with 296 additions and 176 deletions
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue