mirror of https://github.com/icsharpcode/ILSpy.git
21 changed files with 632 additions and 34 deletions
@ -1,12 +1,63 @@
@@ -1,12 +1,63 @@
|
||||
using System; |
||||
using System.Collections; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
using System.Text; |
||||
using System.Threading.Tasks; |
||||
|
||||
namespace ICSharpCode.Decompiler.IL |
||||
{ |
||||
class BlockBuilder |
||||
class BlockBuilder(private readonly Mono.Cecil.Cil.MethodBody body, bool instructionInlining) |
||||
{ |
||||
BlockContainer currentContainer; |
||||
Block currentBlock; |
||||
Stack<ILInstruction> instructionStack = (instructionInlining ? new Stack<ILInstruction>() : null); |
||||
|
||||
public BlockContainer CreateBlocks(List<ILInstruction> instructions, BitArray incomingBranches) |
||||
{ |
||||
currentContainer = new BlockContainer(); |
||||
|
||||
incomingBranches[0] = true; // see entrypoint as incoming branch
|
||||
|
||||
foreach (var inst in instructions) { |
||||
int start = inst.ILRange.Start; |
||||
if (incomingBranches[start]) { |
||||
// Finish up the previous block
|
||||
FinalizeCurrentBlock(start, fallthrough: true); |
||||
// Create the new block
|
||||
currentBlock = new Block(); |
||||
currentContainer.Blocks.Add(currentBlock); |
||||
currentBlock.ILRange = new Interval(start, start); |
||||
} |
||||
if (currentBlock != null) { |
||||
if (instructionStack == null) { |
||||
currentBlock.Instructions.Add(inst); |
||||
} else { |
||||
var inlinedInst = inst.Inline(InstructionFlags.None, instructionStack, out bool finished); |
||||
instructionStack.Push(inlinedInst); |
||||
} |
||||
if (!inst.IsEndReachable) |
||||
FinalizeCurrentBlock(inst.ILRange.End, fallthrough: false); |
||||
} |
||||
} |
||||
FinalizeCurrentBlock(body.CodeSize, fallthrough: false); |
||||
return currentContainer; |
||||
} |
||||
|
||||
private void FinalizeCurrentBlock(int currentILOffset, bool fallthrough) |
||||
{ |
||||
if (currentBlock == null) |
||||
return; |
||||
if (instructionStack != null && instructionStack.Count > 0) { |
||||
// Flush instruction stack
|
||||
currentBlock.Instructions.AddRange(instructionStack.Reverse()); |
||||
instructionStack.Clear(); |
||||
} |
||||
currentBlock.ILRange = new Interval(currentBlock.ILRange.Start, currentILOffset); |
||||
if (fallthrough) |
||||
currentBlock.Instructions.Add(new Branch(OpCode.Branch, currentILOffset)); |
||||
currentBlock = null; |
||||
} |
||||
} |
||||
} |
||||
|
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Text; |
||||
using System.Threading.Tasks; |
||||
|
||||
namespace ICSharpCode.Decompiler.IL |
||||
{ |
||||
[Flags] |
||||
public enum InstructionFlags |
||||
{ |
||||
None = 0, |
||||
MayPop = 0x01, |
||||
MayPeek = 0x02, |
||||
/// <summary>
|
||||
/// The instruction may throw an exception.
|
||||
/// </summary>
|
||||
MayThrow = 0x04, |
||||
/// <summary>
|
||||
/// The instruction may read from local variables.
|
||||
/// </summary>
|
||||
MayReadLocals = 0x08, |
||||
/// <summary>
|
||||
/// The instruction may write to local variables.
|
||||
/// </summary>
|
||||
MayWriteLocals = 0x10, |
||||
/// <summary>
|
||||
/// The instruction may exit with a jump or return.
|
||||
/// </summary>
|
||||
MayJump = 0x20, |
||||
/// <summary>
|
||||
/// The instruction may have side effects, such as writing to heap memory,
|
||||
/// performing system calls, writing to local variables through pointers, etc.
|
||||
/// </summary>
|
||||
SideEffects = 0x40, |
||||
} |
||||
} |
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
using System; |
||||
|
||||
namespace ICSharpCode.Decompiler.IL |
||||
{ |
||||
internal class SemanticHelper |
||||
{ |
||||
/// <summary>
|
||||
/// Gets whether the instruction sequence 'inst1; inst2;' may be ordered to 'inst2; inst1;'
|
||||
/// </summary>
|
||||
internal static bool MayReorder(InstructionFlags inst1, InstructionFlags inst2) |
||||
{ |
||||
// If both instructions perform a non-read-only action, we cannot reorder them
|
||||
if ((inst1 & inst2 & ~(InstructionFlags.MayPeek | InstructionFlags.MayReadLocals)) != 0) |
||||
return false; |
||||
// We cannot reorder if inst2 might pop what inst1 peeks at
|
||||
if (ConflictingPair(inst1, inst2, InstructionFlags.MayPeek, InstructionFlags.MayPop)) |
||||
return false; |
||||
if (ConflictingPair(inst1, inst2, InstructionFlags.MayReadLocals, InstructionFlags.MayWriteLocals)) |
||||
return false; |
||||
return true; |
||||
} |
||||
|
||||
private static bool ConflictingPair(InstructionFlags inst1, InstructionFlags inst2, InstructionFlags readFlag, InstructionFlags writeFlag) |
||||
{ |
||||
// if one instruction has the read flag and the other the write flag, that's a conflict
|
||||
return (inst1 & readFlag) != 0 && (inst2 & writeFlag) != 0 |
||||
|| (inst2 & readFlag) != 0 && (inst1 & writeFlag) != 0; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,85 @@
@@ -0,0 +1,85 @@
|
||||
The first step ICSharpCode.Decompiler performs to decompile a method is to |
||||
translate the IL code into the 'ILAst'. |
||||
|
||||
An ILAst node (ILExpression in the code) usually has other nodes as arguments, |
||||
and performs a computation with the result of those arguments. |
||||
|
||||
A result of a node is either |
||||
* a value (which can be computed on) |
||||
* void (which is invalid as an argument, but nodes in blocks may produce void results) |
||||
* a thrown exception (which stops further evaluation until a matching catch block) |
||||
* the execution of a branch instruction (which also stops evaluation until we reach the block container that contains the branch target) |
||||
|
||||
An ILAst node may also access the IL evaluation stack. When discussing this stack, we will use the notation |
||||
[2, 1, ...] to mean the stack where the value '2' is on top. |
||||
The IL evaluation stack is manipulated by the following instructions: |
||||
* Peek - returns value on top of stack as result, leaves stack unmodified |
||||
* Pop - returns value on top of stack as result, pops the value from the stack |
||||
|
||||
An IL block will evaluate all instructions contained in the block, and will implicitly push the result |
||||
of every instruction to the stack (only if the result is a value). |
||||
For example, starting with an empty stack [], execution of the block: |
||||
{ |
||||
ldc.i4 1 |
||||
ldc.i4 2 |
||||
} |
||||
will result in the stack [2, 1]. |
||||
|
||||
Initially, every IL instruction is converted to a corresponding ILAst instruction that uses 'Pop' instructions as arguments. |
||||
For example, IL 'sub' will become 'sub(pop, pop)'. |
||||
|
||||
This actually poses a problem for the ILAst semantics - we want evaluation as the arguments to happen |
||||
left-to-right (as in C#). Yet, to correctly model the semantics of the IL 'sub' instruction, we need to |
||||
pop all the arguments at once without reversing them. |
||||
Starting with the stack [2, 1], the IL 'sub' instruction produces the result -1! |
||||
But if we evaluated the pop instructions in the left-to-right order, we would get sub(2, 1) = +1. |
||||
|
||||
To demonstrate the effect of the evaluation order, we will use a squaring function with the side |
||||
effect of logging the operation to the console: |
||||
'int square(int val) { Console.WriteLine("{0} squared is {1}", val, val * val); return val*val; }': |
||||
|
||||
Now, the ILAst instruction 'add(square(2), square(3))' will produce the output |
||||
2 squared is 4 |
||||
3 squared is 9 |
||||
and produces the result 13. Note that the evaluation here happens from left to right. |
||||
|
||||
However, consider the program: |
||||
'add(square(pop), square(pop))' |
||||
starting with the stack [3, 2]. |
||||
We want our ILAst instruction to have the same effect as an IL instruction, essentially 'popping all the necessary values at once'. |
||||
This means the expected result is the same as with 'add(square(2), square(3))'. |
||||
Despite the square calls happening left-to-right, we need to execute the pop instructions right-to-left! |
||||
|
||||
Logically, we consider 'pop' to not really be an ILAst instruction, but more like a placeholder for filling in a stack value. |
||||
Therefore, we define the semantics of ILAst instructions in two phases: |
||||
* Phase 1: a right-to-left pass replacing the 'pop' instructions with the values from the stack |
||||
* Phase 2: a left-to-right pass performing the actual evaluation. |
||||
|
||||
Things become even more tricky if we allow for inline blocks within expressions. These may occur for some C# language |
||||
constructs like object initializers. |
||||
For example, consider the ILAst for 'new List<int> { 1 }.Length': |
||||
|
||||
call get_Length( |
||||
{ newobj List<int>() |
||||
call Add(peek, ldc.i4 1) |
||||
}) // inline blocks evaluate to the value they pushed onto the stack |
||||
|
||||
When evaluating the 'call get_Length' instruction, in phase 1 we cannot completely replace all |
||||
'peek' and 'pop' instructions with values from the stack, because the List<int> object is not yet pushed to the stack. |
||||
We use a simple solution to this problem: phase 1 does not traverse into blocks, and only replaces all peek/pop |
||||
instructions reachable without entering a new block. |
||||
When phase 2 of the call get_Length then actually evaluates the nested block, the block runs |
||||
phase 1 for its first instruction, then phase 2 for the first instruction, then pushes the result (if its a value), |
||||
and then starts the same process again at phase 1 for the second instruction. |
||||
|
||||
Note that this whole discussion was only necessary in order to have clear semantics for every possible ILAst. |
||||
These tricky semantics are mostly irrelevant for the actual ILAsts occurring during decompilation. |
||||
This is because initially all instructions start with their 'pop' placeholders being in a contiguous sequence |
||||
at the beginning of their left-to-right evaluation order. |
||||
Because the inlining step that takes an instruction from a block and uses it to replace the matching 'pop' placeholder |
||||
in the following instruction has to put that instruction into the first 'pop' in phase1-order, it will always |
||||
replace the right-most 'pop', which is the last 'pop' in phase-2 evaluation order. This means |
||||
the remaining placeholders stay a contiguous sequence at the beginning of their left-to-right evaluation order. |
||||
|
||||
It does have some implications on inlining, though: we cannot inline blocks that look at more stack values |
||||
than just the ones they push themselves. |
Loading…
Reference in new issue