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.
367 lines
14 KiB
367 lines
14 KiB
// Copyright (c) 2017 Siegfried Pammer |
|
// |
|
// 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.Linq; |
|
using System.Text; |
|
using ICSharpCode.Decompiler.Util; |
|
|
|
namespace ICSharpCode.Decompiler.IL.Transforms |
|
{ |
|
/// <summary> |
|
/// If possible, transforms plain ILAst loops into while (condition), do-while and for-loops. |
|
/// For the invariants of the transforms <see cref="BlockContainer.CheckInvariant(ILPhase)"/>. |
|
/// </summary> |
|
public class HighLevelLoopTransform : IILTransform |
|
{ |
|
ILTransformContext context; |
|
|
|
public void Run(ILFunction function, ILTransformContext context) |
|
{ |
|
this.context = context; |
|
|
|
foreach (var loop in function.Descendants.OfType<BlockContainer>()) { |
|
if (loop.Kind != ContainerKind.Loop) |
|
continue; |
|
if (MatchWhileLoop(loop, out var condition, out var loopBody)) { |
|
MatchForLoop(loop, condition, loopBody); |
|
continue; |
|
} |
|
if (MatchDoWhileLoop(loop)) |
|
continue; |
|
} |
|
} |
|
|
|
bool MatchWhileLoop(BlockContainer loop, out IfInstruction condition, out Block loopBody) |
|
{ |
|
// while-loop: |
|
// if (loop-condition) br loop-content-block |
|
// leave loop-container |
|
// -or- |
|
// if (loop-condition) block content-block |
|
// leave loop-container |
|
condition = null; |
|
loopBody = null; |
|
if (loop.EntryPoint.Instructions.Count != 2) |
|
return false; |
|
if (!(loop.EntryPoint.Instructions[0] is IfInstruction ifInstruction)) |
|
return false; |
|
if (!ifInstruction.FalseInst.MatchNop()) |
|
return false; |
|
condition = ifInstruction; |
|
var trueInst = ifInstruction.TrueInst; |
|
if (!loop.EntryPoint.Instructions[1].MatchLeave(loop)) |
|
return false; |
|
if (trueInst is Block b) { |
|
context.Step("Transform to while (condition) loop", loop); |
|
// move the loop body to its own block: |
|
loopBody = b; |
|
trueInst.ReplaceWith(new Branch(loopBody)); |
|
loop.Blocks.Insert(1, loopBody); |
|
// sometimes the loop-content-block does not end with a leave/branch |
|
if (!loopBody.HasFlag(InstructionFlags.EndPointUnreachable)) |
|
loopBody.Instructions.Add(new Leave(loop)); |
|
} else if (trueInst is Branch br) { |
|
context.Step("Transform to while (condition) loop", loop); |
|
loopBody = br.TargetBlock; |
|
} else { |
|
return false; |
|
} |
|
// move the branch/leave instruction into the condition block |
|
ifInstruction.FalseInst = loop.EntryPoint.Instructions[1]; |
|
loop.EntryPoint.Instructions.RemoveAt(1); |
|
loop.Kind = ContainerKind.While; |
|
return true; |
|
} |
|
|
|
void SplitConditions(ILInstruction expression, List<ILInstruction> conditions) |
|
{ |
|
if (expression.MatchLogicAnd(out var l, out var r)) { |
|
SplitConditions(l, conditions); |
|
SplitConditions(r, conditions); |
|
} else { |
|
conditions.Add(expression); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Matches a do-while loop and performs the following transformations: |
|
/// - combine all compatible conditions into one IfInstruction. |
|
/// - extract conditions into a condition block, or move the existing condition block to the end. |
|
/// </summary> |
|
bool MatchDoWhileLoop(BlockContainer loop) |
|
{ |
|
(List<IfInstruction> conditions, ILInstruction exit, bool swap, bool split) = AnalyzeDoWhileConditions(loop); |
|
// not a do-while loop, exit. |
|
if (conditions == null || conditions.Count == 0) |
|
return false; |
|
context.Step("Transform to do-while loop", loop); |
|
Block conditionBlock; |
|
// first we remove all extracted instructions from the original block. |
|
var originalBlock = (Block)exit.Parent; |
|
originalBlock.Instructions.RemoveRange(originalBlock.Instructions.Count - conditions.Count - 1, conditions.Count + 1); |
|
// we need to split the block: |
|
if (split) { |
|
// add a new block at the end and add a branch to the new block. |
|
conditionBlock = new Block(); |
|
loop.Blocks.Add(conditionBlock); |
|
originalBlock.Instructions.Add(new Branch(conditionBlock)); |
|
} else { |
|
// move the condition block to the end. |
|
conditionBlock = originalBlock; |
|
loop.Blocks.MoveElementToEnd(originalBlock); |
|
} |
|
// combine all conditions and the exit instruction into one IfInstruction: |
|
IfInstruction condition = null; |
|
conditionBlock.AddILRange(exit.ILRange); |
|
foreach (var inst in conditions) { |
|
conditionBlock.AddILRange(inst.ILRange); |
|
if (condition == null) { |
|
condition = inst; |
|
if (swap) { |
|
// branches must be swapped and condition negated: |
|
condition.Condition = Comp.LogicNot(condition.Condition); |
|
condition.FalseInst = condition.TrueInst; |
|
condition.TrueInst = exit; |
|
} else { |
|
condition.FalseInst = exit; |
|
} |
|
} else { |
|
if (swap) { |
|
condition.Condition = IfInstruction.LogicAnd(Comp.LogicNot(inst.Condition), condition.Condition); |
|
} else { |
|
condition.Condition = IfInstruction.LogicAnd(inst.Condition, condition.Condition); |
|
} |
|
} |
|
} |
|
// insert the combined conditions into the condition block: |
|
conditionBlock.Instructions.Add(condition); |
|
// simplify the condition: |
|
new ExpressionTransforms().Run(conditionBlock, condition.ChildIndex, new StatementTransformContext(new BlockTransformContext(context))); |
|
// transform complete |
|
loop.Kind = ContainerKind.DoWhile; |
|
return true; |
|
} |
|
|
|
static (List<IfInstruction> conditions, ILInstruction exit, bool swap, bool split) AnalyzeDoWhileConditions(BlockContainer loop) |
|
{ |
|
bool swap; |
|
// we iterate over all blocks from the bottom, because the entry-point |
|
// should only be considered as condition block, if there are no other blocks. |
|
foreach (var block in loop.Blocks.Reverse()) { |
|
// first we match the end of the block: |
|
if (MatchDoWhileConditionBlock(loop, block, out swap)) { |
|
// now collect all instructions that are usable as loop conditions |
|
var conditions = CollectConditions(loop, block, swap); |
|
// split only if the block is either the entry-point or contains other instructions as well. |
|
var split = block == loop.EntryPoint || block.Instructions.Count > conditions.Count + 1; // + 1 is the final leave/branch. |
|
return (conditions, block.Instructions.Last(), swap, split); |
|
} |
|
} |
|
return (null, null, false, false); |
|
} |
|
|
|
/// <summary> |
|
/// Returns a list of all IfInstructions that can be used as loop conditon, i.e., |
|
/// that have no false-instruction and have leave loop (if swapped) or branch entry-point as true-instruction. |
|
/// </summary> |
|
static List<IfInstruction> CollectConditions(BlockContainer loop, Block block, bool swap) |
|
{ |
|
var list = new List<IfInstruction>(); |
|
int i = block.Instructions.Count - 2; |
|
while (i >= 0 && block.Instructions[i] is IfInstruction ifInst) { |
|
if (!ifInst.FalseInst.MatchNop()) |
|
break; |
|
if (swap) { |
|
if (!ifInst.TrueInst.MatchLeave(loop)) |
|
break; |
|
list.Add(ifInst); |
|
} else { |
|
if (!ifInst.TrueInst.MatchBranch(loop.EntryPoint)) |
|
break; |
|
list.Add(ifInst); |
|
} |
|
i--; |
|
} |
|
|
|
return list; |
|
} |
|
|
|
static bool MatchDoWhileConditionBlock(BlockContainer loop, Block block, out bool swapBranches) |
|
{ |
|
// match the end of the block: |
|
// if (condition) branch entry-point else nop |
|
// leave loop |
|
// -or- |
|
// if (condition) leave loop else nop |
|
// branch entry-point |
|
swapBranches = false; |
|
// empty block? |
|
if (block.Instructions.Count < 2) |
|
return false; |
|
var last = block.Instructions.Last(); |
|
var ifInstruction = block.Instructions.SecondToLastOrDefault() as IfInstruction; |
|
// no IfInstruction or already transformed? |
|
if (ifInstruction == null || !ifInstruction.FalseInst.MatchNop()) |
|
return false; |
|
// if the last instruction is a branch |
|
// we assume the branch instructions need to be swapped. |
|
if (last.MatchBranch(loop.EntryPoint)) |
|
swapBranches = true; |
|
else if (last.MatchLeave(loop)) |
|
swapBranches = false; |
|
else return false; |
|
// match the IfInstruction |
|
if (swapBranches) { |
|
if (!ifInstruction.TrueInst.MatchLeave(loop)) |
|
return false; |
|
} else { |
|
if (!ifInstruction.TrueInst.MatchBranch(loop.EntryPoint)) |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
bool MatchForLoop(BlockContainer loop, IfInstruction whileCondition, Block whileLoopBody) |
|
{ |
|
// for loops have exactly two incoming edges at the entry point. |
|
if (loop.EntryPoint.IncomingEdgeCount != 2) |
|
return false; |
|
// try to find an increment block: |
|
// consists of simple statements only. |
|
var incrementBlock = loop.Blocks.SingleOrDefault( |
|
b => b != whileLoopBody |
|
&& b.Instructions.Last().MatchBranch(loop.EntryPoint) |
|
&& b.Instructions.SkipLast(1).All(IsSimpleStatement)); |
|
if (incrementBlock != null) { |
|
// we found a possible increment block, just make sure, that there are at least three blocks: |
|
// - condition block |
|
// - loop body |
|
// - increment block |
|
if (incrementBlock.Instructions.Count <= 1 || loop.Blocks.Count < 3) |
|
return false; |
|
context.Step("Transform to for loop", loop); |
|
// move the block to the end of the loop: |
|
loop.Blocks.MoveElementToEnd(incrementBlock); |
|
loop.Kind = ContainerKind.For; |
|
} else { |
|
// we need to move the increment statements into its own block: |
|
// last must be a branch entry-point |
|
var last = whileLoopBody.Instructions.LastOrDefault(); |
|
var secondToLast = whileLoopBody.Instructions.SecondToLastOrDefault(); |
|
if (last == null || secondToLast == null) |
|
return false; |
|
if (!last.MatchBranch(loop.EntryPoint)) |
|
return false; |
|
// we only deal with 'numeric' increments |
|
if (!MatchIncrement(secondToLast, out var incrementVariable)) |
|
return false; |
|
// the increment variable must be local/stack variable |
|
if (incrementVariable.Kind == VariableKind.Parameter) |
|
return false; |
|
// split conditions: |
|
var conditions = new List<ILInstruction>(); |
|
SplitConditions(whileCondition.Condition, conditions); |
|
IfInstruction forCondition = null; |
|
int numberOfConditions = 0; |
|
foreach (var condition in conditions) { |
|
// the increment variable must be used in the condition |
|
if (!condition.Descendants.Any(inst => inst.MatchLdLoc(incrementVariable))) |
|
break; |
|
// condition should not contain an assignment |
|
if (condition.Descendants.Any(IsAssignment)) |
|
break; |
|
if (forCondition == null) { |
|
forCondition = new IfInstruction(condition, whileCondition.TrueInst, whileCondition.FalseInst); |
|
} else { |
|
forCondition.Condition = IfInstruction.LogicAnd(forCondition.Condition, condition); |
|
} |
|
numberOfConditions++; |
|
} |
|
if (numberOfConditions == 0) |
|
return false; |
|
context.Step("Transform to for loop", loop); |
|
// split condition block: |
|
whileCondition.ReplaceWith(forCondition); |
|
new ExpressionTransforms().Run(loop.EntryPoint, forCondition.ChildIndex, new StatementTransformContext(new BlockTransformContext(context))); |
|
for (int i = conditions.Count - 1; i >= numberOfConditions; i--) { |
|
whileLoopBody.Instructions.Insert(0, new IfInstruction(Comp.LogicNot(conditions[i]), new Leave(loop))); |
|
new ExpressionTransforms().Run(whileLoopBody, 0, new StatementTransformContext(new BlockTransformContext(context))); |
|
} |
|
// create a new increment block and add it at the end: |
|
int secondToLastIndex = secondToLast.ChildIndex; |
|
var newIncremenBlock = new Block(); |
|
loop.Blocks.Add(newIncremenBlock); |
|
// move the increment instruction: |
|
newIncremenBlock.Instructions.Add(secondToLast); |
|
newIncremenBlock.Instructions.Add(last); |
|
newIncremenBlock.AddILRange(secondToLast.ILRange); |
|
whileLoopBody.Instructions.RemoveRange(secondToLastIndex, 2); |
|
whileLoopBody.Instructions.Add(new Branch(newIncremenBlock)); |
|
// complete transform. |
|
loop.Kind = ContainerKind.For; |
|
} |
|
return true; |
|
} |
|
|
|
bool IsAssignment(ILInstruction inst) |
|
{ |
|
if (inst is StLoc) |
|
return true; |
|
if (inst is CompoundAssignmentInstruction) |
|
return true; |
|
return false; |
|
} |
|
|
|
/// <summary> |
|
/// Returns true if the instruction is stloc v(add(ldloc v, arg)) |
|
/// or stloc v(compound.assign(ldloc v, arg)) |
|
/// </summary> |
|
public static bool MatchIncrement(ILInstruction inst, out ILVariable variable) |
|
{ |
|
if (!inst.MatchStLoc(out variable, out var value)) |
|
return false; |
|
if (!value.MatchBinaryNumericInstruction(BinaryNumericOperator.Add, out var left, out var right)) { |
|
if (value is CompoundAssignmentInstruction cai) { |
|
left = cai.Target; |
|
} else return false; |
|
} |
|
return left.MatchLdLoc(variable); |
|
} |
|
|
|
/// <summary> |
|
/// Gets whether the statement is 'simple' (usable as for loop iterator): |
|
/// Currently we only accept calls and assignments. |
|
/// </summary> |
|
static bool IsSimpleStatement(ILInstruction inst) |
|
{ |
|
switch (inst.OpCode) { |
|
case OpCode.Call: |
|
case OpCode.CallVirt: |
|
case OpCode.NewObj: |
|
case OpCode.StLoc: |
|
case OpCode.StObj: |
|
case OpCode.CompoundAssignmentInstruction: |
|
return true; |
|
default: |
|
return false; |
|
} |
|
} |
|
} |
|
}
|
|
|