mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
155 lines
6.6 KiB
155 lines
6.6 KiB
// 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.Collections.Generic; |
|
using System.Diagnostics; |
|
using System.Linq; |
|
using ICSharpCode.Decompiler.IL.Transforms; |
|
|
|
namespace ICSharpCode.Decompiler.IL.ControlFlow |
|
{ |
|
/// <summary> |
|
/// This transform 'optimizes' the control flow logic in the IL code: |
|
/// it replaces constructs that are generated by the C# compiler in debug mode |
|
/// with shorter constructs that are more straightforward to analyze. |
|
/// </summary> |
|
/// <remarks> |
|
/// The transformations performed are: |
|
/// * 'nop' instructions are removed |
|
/// * branches that lead to other branches are replaced with branches that directly jump to the destination |
|
/// * branches that lead to a 'return block' are replaced with a return instruction |
|
/// * basic blocks are combined where possible |
|
/// </remarks> |
|
public class ControlFlowSimplification : IILTransform |
|
{ |
|
public void Run(ILFunction function, ILTransformContext context) |
|
{ |
|
foreach (var block in function.Descendants.OfType<Block>()) { |
|
// Remove 'nop' instructions |
|
block.Instructions.RemoveAll(inst => inst.OpCode == OpCode.Nop); |
|
|
|
InlineReturnBlock(block); |
|
// 1st pass SimplifySwitchInstruction before SimplifyBranchChains() |
|
// starts duplicating return instructions. |
|
SwitchDetection.SimplifySwitchInstruction(block); |
|
} |
|
SimplifyBranchChains(function); |
|
CleanUpEmptyBlocks(function); |
|
} |
|
|
|
void InlineReturnBlock(Block block) |
|
{ |
|
// In debug mode, the C#-compiler generates 'return blocks' that |
|
// unnecessarily store the return value to a local and then load it again: |
|
// v = <inst> |
|
// ret(v) |
|
// (where 'v' has no other uses) |
|
// Simplify these to a simple `ret(<inst>)` so that they match the release build version. |
|
// |
|
if (block.Instructions.Count == 2 && block.Instructions[1].OpCode == OpCode.Return) { |
|
Return ret = (Return)block.Instructions[1]; |
|
ILVariable v; |
|
ILInstruction inst; |
|
if (ret.ReturnValue != null && ret.ReturnValue.MatchLdLoc(out v) |
|
&& v.IsSingleDefinition && v.LoadCount == 1 && block.Instructions[0].MatchStLoc(v, out inst)) |
|
{ |
|
inst.AddILRange(ret.ReturnValue.ILRange); |
|
inst.AddILRange(block.Instructions[0].ILRange); |
|
ret.ReturnValue = inst; |
|
block.Instructions.RemoveAt(0); |
|
} |
|
} |
|
} |
|
|
|
void SimplifyBranchChains(ILFunction function) |
|
{ |
|
HashSet<Block> visitedBlocks = new HashSet<Block>(); |
|
foreach (var branch in function.Descendants.OfType<Branch>()) { |
|
// Resolve chained branches to the final target: |
|
var targetBlock = branch.TargetBlock; |
|
visitedBlocks.Clear(); |
|
while (targetBlock.Instructions.Count == 1 && targetBlock.Instructions[0].OpCode == OpCode.Branch) { |
|
if (!visitedBlocks.Add(targetBlock)) { |
|
// prevent infinite loop when branch chain is cyclic |
|
break; |
|
} |
|
var nextBranch = (Branch)targetBlock.Instructions[0]; |
|
branch.TargetBlock = nextBranch.TargetBlock; |
|
branch.AddILRange(nextBranch.ILRange); |
|
if (targetBlock.IncomingEdgeCount == 0) |
|
targetBlock.Instructions.Clear(); // mark the block for deletion |
|
targetBlock = branch.TargetBlock; |
|
} |
|
if (IsReturnBlock(targetBlock)) { |
|
// Replace branches to 'return blocks' with the return instruction |
|
branch.ReplaceWith(targetBlock.Instructions[0].Clone()); |
|
} else if (targetBlock.Instructions.Count == 1 && targetBlock.Instructions[0].OpCode == OpCode.Leave) { |
|
// Replace branches to 'leave' instruction with the leave instruction |
|
Leave leave = (Leave)targetBlock.Instructions[0]; |
|
branch.ReplaceWith(new Leave(leave.TargetContainer) { ILRange = branch.ILRange }); |
|
} |
|
if (targetBlock.IncomingEdgeCount == 0) |
|
targetBlock.Instructions.Clear(); // mark the block for deletion |
|
} |
|
} |
|
|
|
void CleanUpEmptyBlocks(ILFunction function) |
|
{ |
|
foreach (var container in function.Descendants.OfType<BlockContainer>()) { |
|
foreach (var block in container.Blocks) { |
|
if (block.Instructions.Count == 0) |
|
continue; // block is already marked for deletion |
|
while (CombineBlockWithNextBlock(container, block)) { |
|
// repeat combining blocks until it is no longer possible |
|
// (this loop terminates because a block is deleted in every iteration) |
|
} |
|
} |
|
// Remove return blocks that are no longer reachable: |
|
container.Blocks.RemoveAll(b => b.IncomingEdgeCount == 0 && b.Instructions.Count == 0); |
|
} |
|
} |
|
|
|
static bool IsReturnBlock(Block targetBlock) |
|
{ |
|
if (targetBlock.Instructions.Count != 1 || targetBlock.FinalInstruction.OpCode != OpCode.Nop) |
|
return false; |
|
var ret = targetBlock.Instructions[0] as Return; |
|
return ret != null && (ret.ReturnValue == null || ret.ReturnValue.OpCode == OpCode.LdLoc); |
|
} |
|
|
|
static bool CombineBlockWithNextBlock(BlockContainer container, Block block) |
|
{ |
|
Debug.Assert(container == block.Parent); |
|
// Ensure the block will stay a basic block -- we don't want extended basic blocks prior to LoopDetection. |
|
if (block.Instructions.Count > 1 && block.Instructions[block.Instructions.Count - 2].HasFlag(InstructionFlags.MayBranch)) |
|
return false; |
|
Branch br = block.Instructions.Last() as Branch; |
|
// Check whether we can combine the target block with this block |
|
if (br == null || br.TargetBlock.Parent != container || br.TargetBlock.IncomingEdgeCount != 1) |
|
return false; |
|
if (br.TargetBlock == block) |
|
return false; // don't inline block into itself |
|
var targetBlock = br.TargetBlock; |
|
block.Instructions.Remove(br); |
|
block.Instructions.AddRange(targetBlock.Instructions); |
|
targetBlock.Instructions.Clear(); // mark targetBlock for deletion |
|
return true; |
|
} |
|
|
|
} |
|
}
|
|
|