.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!
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

// 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;
}
}
}