Browse Source

expr inlining

pull/728/head
Daniel Grunwald 11 years ago
parent
commit
817b0baded
  1. 45
      ICSharpCode.Decompiler/Disassembler/MethodBodyDisassembler.cs
  2. 2
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  3. 53
      ICSharpCode.Decompiler/IL/BlockBuilder.cs
  4. 12
      ICSharpCode.Decompiler/IL/ILReader.cs
  5. 37
      ICSharpCode.Decompiler/IL/InstructionFlags.cs
  6. 19
      ICSharpCode.Decompiler/IL/Instructions/BinaryInstruction.cs
  7. 107
      ICSharpCode.Decompiler/IL/Instructions/Block.cs
  8. 12
      ICSharpCode.Decompiler/IL/Instructions/BoxInstructions.cs
  9. 37
      ICSharpCode.Decompiler/IL/Instructions/BranchInstruction.cs
  10. 28
      ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs
  11. 22
      ICSharpCode.Decompiler/IL/Instructions/FieldAccess.cs
  12. 11
      ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs
  13. 10
      ICSharpCode.Decompiler/IL/Instructions/MemoryInstruction.cs
  14. 8
      ICSharpCode.Decompiler/IL/Instructions/OpCode.cs
  15. 74
      ICSharpCode.Decompiler/IL/Instructions/SimpleInstruction.cs
  16. 35
      ICSharpCode.Decompiler/IL/Instructions/UnaryInstruction.cs
  17. 18
      ICSharpCode.Decompiler/IL/Instructions/VarInstructions.cs
  18. 30
      ICSharpCode.Decompiler/IL/SemanticHelper.cs
  19. 2
      ILSpy.sln
  20. 19
      ILSpy/Languages/ILAstLanguage.cs
  21. 85
      doc/ILAst.txt

45
ICSharpCode.Decompiler/Disassembler/MethodBodyDisassembler.cs

@ -55,26 +55,7 @@ namespace ICSharpCode.Decompiler.Disassembler @@ -55,26 +55,7 @@ namespace ICSharpCode.Decompiler.Disassembler
if (method.DeclaringType.Module.Assembly != null && method.DeclaringType.Module.Assembly.EntryPoint == method)
output.WriteLine(".entrypoint");
if (method.Body.HasVariables) {
output.Write(".locals ");
if (method.Body.InitLocals)
output.Write("init ");
output.WriteLine("(");
output.Indent();
foreach (var v in method.Body.Variables) {
output.WriteDefinition("[" + v.Index + "] ", v);
v.VariableType.WriteTo(output);
if (!string.IsNullOrEmpty(v.Name)) {
output.Write(' ');
output.Write(DisassemblerHelpers.Escape(v.Name));
}
if (v.Index + 1 < method.Body.Variables.Count)
output.Write(',');
output.WriteLine();
}
output.Unindent();
output.WriteLine(")");
}
DisassembleLocalsBlock(body);
output.WriteLine();
if (detectControlStructure && body.Instructions.Count > 0) {
@ -104,6 +85,30 @@ namespace ICSharpCode.Decompiler.Disassembler @@ -104,6 +85,30 @@ namespace ICSharpCode.Decompiler.Disassembler
}
}
private void DisassembleLocalsBlock(MethodBody body)
{
if (body.HasVariables) {
output.Write(".locals ");
if (body.InitLocals)
output.Write("init ");
output.WriteLine("(");
output.Indent();
foreach (var v in body.Variables) {
output.WriteDefinition("[" + v.Index + "] ", v);
v.VariableType.WriteTo(output);
if (!string.IsNullOrEmpty(v.Name)) {
output.Write(' ');
output.Write(DisassemblerHelpers.Escape(v.Name));
}
if (v.Index + 1 < body.Variables.Count)
output.Write(',');
output.WriteLine();
}
output.Unindent();
output.WriteLine(")");
}
}
internal void WriteExceptionHandlers(MethodBody body)
{
if (body.HasExceptionHandlers) {

2
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -70,6 +70,7 @@ @@ -70,6 +70,7 @@
<Compile Include="Disassembler\ILStructure.cs" />
<Compile Include="Disassembler\MethodBodyDisassembler.cs" />
<Compile Include="Disassembler\ReflectionDisassembler.cs" />
<Compile Include="IL\SemanticHelper.cs" />
<Compile Include="IL\BlockBuilder.cs" />
<Compile Include="IL\ILOpCodes.cs">
<AutoGen>True</AutoGen>
@ -79,6 +80,7 @@ @@ -79,6 +80,7 @@
<Compile Include="IL\ILReader.cs" />
<Compile Include="IL\ILTypeExtensions.cs" />
<Compile Include="IL\ILVariable.cs" />
<Compile Include="IL\InstructionFlags.cs" />
<Compile Include="IL\InstructionOutputExtensions.cs" />
<Compile Include="IL\Instructions\BinaryInstruction.cs" />
<Compile Include="IL\Instructions\Block.cs" />

53
ICSharpCode.Decompiler/IL/BlockBuilder.cs

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

12
ICSharpCode.Decompiler/IL/ILReader.cs

@ -114,6 +114,18 @@ namespace ICSharpCode.Decompiler.IL @@ -114,6 +114,18 @@ namespace ICSharpCode.Decompiler.IL
new Disassembler.MethodBodyDisassembler(output, false, cancellationToken).WriteExceptionHandlers(body);
}
public void WriteBlocks(ITextOutput output, bool instructionInlining)
{
CreateBlocks(instructionInlining).WriteTo(output);
}
BlockContainer CreateBlocks(bool instructionInlining)
{
if (instructionBuilder == null)
ReadInstructions(null);
return new BlockBuilder(body, instructionInlining).CreateBlocks(instructionBuilder, isBranchTarget);
}
ILInstruction DecodeInstruction()
{
var ilOpCode = ReadOpCode(ref reader);

37
ICSharpCode.Decompiler/IL/InstructionFlags.cs

@ -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,
}
}

19
ICSharpCode.Decompiler/IL/Instructions/BinaryInstruction.cs

@ -18,6 +18,15 @@ namespace ICSharpCode.Decompiler.IL @@ -18,6 +18,15 @@ namespace ICSharpCode.Decompiler.IL
Left = transformFunc(Left);
Right = transformFunc(Right);
}
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished)
{
InstructionFlags flagsBeforeRight = flagsBefore | (Left.Flags & ~(InstructionFlags.MayPeek | InstructionFlags.MayPop));
Right = Right.Inline(flagsBeforeRight, instructionStack, out finished);
if (finished)
Left = Left.Inline(flagsBefore, instructionStack, out finished);
return this;
}
}
class BinaryNumericInstruction(OpCode opCode, StackType opType, OverflowMode overflowMode)
@ -38,6 +47,11 @@ namespace ICSharpCode.Decompiler.IL @@ -38,6 +47,11 @@ namespace ICSharpCode.Decompiler.IL
Right.WriteTo(output);
output.Write(')');
}
public override InstructionFlags Flags
{
get { return Left.Flags | Right.Flags | InstructionFlags.MayThrow; }
}
}
class BinaryComparisonInstruction(OpCode opCode, StackType opType)
@ -56,6 +70,11 @@ namespace ICSharpCode.Decompiler.IL @@ -56,6 +70,11 @@ namespace ICSharpCode.Decompiler.IL
Right.WriteTo(output);
output.Write(')');
}
public override InstructionFlags Flags
{
get { return Left.Flags | Right.Flags; }
}
}
public enum OverflowMode : byte

107
ICSharpCode.Decompiler/IL/Instructions/Block.cs

@ -4,9 +4,112 @@ using System.Linq; @@ -4,9 +4,112 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ICSharpCode.Decompiler.IL.Instructions
namespace ICSharpCode.Decompiler.IL
{
class Block
/// <summary>
/// A block of IL instructions.
/// </summary>
class Block() : ILInstruction(OpCode.Block)
{
public readonly List<ILInstruction> Instructions = new List<ILInstruction>();
public override bool IsPeeking { get { return Instructions.Count > 0 && Instructions[0].IsPeeking; } }
public override bool NoResult { get { return true; } }
public override void TransformChildren(Func<ILInstruction, ILInstruction> transformFunc)
{
for (int i = 0; i < Instructions.Count; i++) {
Instructions[i] = transformFunc(Instructions[i]);
}
}
public override void WriteTo(ITextOutput output)
{
output.WriteDefinition("Block " + Disassembler.DisassemblerHelpers.OffsetToString(this.ILRange.Start), this);
output.WriteLine(" {");
output.Indent();
foreach (var inst in Instructions) {
inst.WriteTo(output);
output.WriteLine();
}
output.Unindent();
output.WriteLine("}");
}
public override InstructionFlags Flags
{
get {
InstructionFlags flags = InstructionFlags.None;
for (int i = 0; i < Instructions.Count; i++) {
flags |= Instructions[i].Flags;
}
return flags;
}
}
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished)
{
if (Instructions.Count > 0)
Instructions[0] = Instructions[0].Inline(flagsBefore, instructionStack, out finished);
else
finished = true;
return this;
}
}
/// <summary>
/// A container of IL blocks.
/// Each block is an extended basic block (branches may only jump to the beginning of blocks, not into the middle),
/// and only branches within this container may reference the blocks in this container.
/// That means that viewed from the outside, the block container has a single entry point (but possibly multiple exit points),
/// and the same holds for every block within the container.
/// </summary>
class BlockContainer() : ILInstruction(OpCode.BlockContainer)
{
public List<Block> Blocks = new List<Block>();
public Block EntryPoint { get { return Blocks[0]; } }
public override bool IsPeeking { get { return EntryPoint.IsPeeking; } }
public override bool NoResult { get { return true; } }
public override void TransformChildren(Func<ILInstruction, ILInstruction> transformFunc)
{
for (int i = 0; i < Blocks.Count; i++) {
if (transformFunc(Blocks[i]) != Blocks[i])
throw new InvalidOperationException("Cannot replace blocks");
}
}
public override void WriteTo(ITextOutput output)
{
output.WriteLine("BlockContainer {");
output.Indent();
foreach (var inst in Blocks) {
inst.WriteTo(output);
output.WriteLine();
}
output.Unindent();
output.WriteLine("}");
}
public override InstructionFlags Flags
{
get
{
InstructionFlags flags = InstructionFlags.None;
for (int i = 0; i < Blocks.Count; i++) {
flags |= Blocks[i].Flags;
}
return flags;
}
}
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished)
{
finished = false;
return this;
}
}
}

12
ICSharpCode.Decompiler/IL/Instructions/BoxInstructions.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using Mono.Cecil;
using ICSharpCode.Decompiler.Disassembler;
using Mono.Cecil;
using System;
using System.Collections.Generic;
using System.Linq;
@ -13,10 +14,15 @@ namespace ICSharpCode.Decompiler.IL @@ -13,10 +14,15 @@ namespace ICSharpCode.Decompiler.IL
{
output.Write(OpCode);
output.Write(' ');
Disassembler.DisassemblerHelpers.WriteOperand(output, TypeReference);
output.Write('(');
TypeReference.WriteTo(output);
output.Write('(');
Operand.WriteTo(output);
output.Write(')');
}
public override InstructionFlags Flags
{
get { return Operand.Flags | InstructionFlags.MayThrow | InstructionFlags.SideEffects; }
}
}
}

37
ICSharpCode.Decompiler/IL/Instructions/BranchInstruction.cs

@ -32,6 +32,17 @@ namespace ICSharpCode.Decompiler.IL @@ -32,6 +32,17 @@ namespace ICSharpCode.Decompiler.IL
public override void TransformChildren(Func<ILInstruction, ILInstruction> transformFunc)
{
}
public override InstructionFlags Flags
{
get { return InstructionFlags.MayJump; }
}
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished)
{
finished = true;
return this;
}
}
/// <summary>
@ -54,20 +65,46 @@ namespace ICSharpCode.Decompiler.IL @@ -54,20 +65,46 @@ namespace ICSharpCode.Decompiler.IL
{
Condition = transformFunc(Condition);
}
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished)
{
Condition = Condition.Inline(flagsBefore, instructionStack, out finished);
return this;
}
public override InstructionFlags Flags
{
get { return InstructionFlags.MayJump | Condition.Flags; }
}
}
class ReturnVoidInstruction() : SimpleInstruction(OpCode.Ret)
{
public override bool IsEndReachable { get { return false; } }
public override InstructionFlags Flags
{
get { return InstructionFlags.MayJump; }
}
}
class ReturnInstruction() : UnaryInstruction(OpCode.Ret)
{
public override bool IsEndReachable { get { return false; } }
public override InstructionFlags Flags
{
get { return InstructionFlags.MayJump | Operand.Flags; }
}
}
class ThrowInstruction() : UnaryInstruction(OpCode.Throw)
{
public override bool IsEndReachable { get { return false; } }
public override InstructionFlags Flags
{
get { return InstructionFlags.MayJump | Operand.Flags; }
}
}
}

28
ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs

@ -72,5 +72,33 @@ namespace ICSharpCode.Decompiler.IL @@ -72,5 +72,33 @@ namespace ICSharpCode.Decompiler.IL
}
output.Write(')');
}
public override InstructionFlags Flags
{
get
{
InstructionFlags flags = InstructionFlags.SideEffects | InstructionFlags.MayThrow;
for (int i = 0; i < Operands.Length; i++) {
flags |= Operands[i].Flags;
}
return flags;
}
}
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished)
{
InstructionFlags operandFlags = InstructionFlags.None;
for (int i = 0; i < Operands.Length - 1; i++) {
operandFlags |= Operands[i].Flags;
}
flagsBefore |= operandFlags & ~(InstructionFlags.MayPeek | InstructionFlags.MayPop);
finished = true;
for (int i = Operands.Length - 1; i >= 0; i--) {
Operands[i] = Operands[i].Inline(flagsBefore, instructionStack, out finished);
if (!finished)
break;
}
return this;
}
}
}

22
ICSharpCode.Decompiler/IL/Instructions/FieldAccess.cs

@ -7,7 +7,7 @@ using System.Threading.Tasks; @@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace ICSharpCode.Decompiler.IL
{
class LoadStaticField(FieldReference field, OpCode opCode = OpCode.Ldsfld) : UnaryInstruction(opCode), ISupportsVolatilePrefix
class LoadStaticField(FieldReference field, OpCode opCode = OpCode.Ldsfld) : SimpleInstruction(opCode), ISupportsVolatilePrefix
{
public readonly FieldReference Field = field;
@ -21,6 +21,11 @@ namespace ICSharpCode.Decompiler.IL @@ -21,6 +21,11 @@ namespace ICSharpCode.Decompiler.IL
output.Write(' ');
Disassembler.DisassemblerHelpers.WriteOperand(output, Field);
}
public override InstructionFlags Flags
{
get { return InstructionFlags.SideEffects; }
}
}
class StoreStaticField(FieldReference field) : UnaryInstruction(OpCode.Stsfld), ISupportsVolatilePrefix
@ -42,6 +47,11 @@ namespace ICSharpCode.Decompiler.IL @@ -42,6 +47,11 @@ namespace ICSharpCode.Decompiler.IL
Operand.WriteTo(output);
output.Write(')');
}
public override InstructionFlags Flags
{
get { return InstructionFlags.SideEffects | Operand.Flags; }
}
}
class LoadInstanceField(FieldReference field, OpCode opCode = OpCode.Ldfld) : UnaryInstruction(opCode), ISupportsVolatilePrefix
@ -61,6 +71,11 @@ namespace ICSharpCode.Decompiler.IL @@ -61,6 +71,11 @@ namespace ICSharpCode.Decompiler.IL
Operand.WriteTo(output);
output.Write(')');
}
public override InstructionFlags Flags
{
get { return InstructionFlags.SideEffects | InstructionFlags.MayThrow | Operand.Flags; }
}
}
class StoreInstanceField(FieldReference field, OpCode opCode = OpCode.Stfld) : BinaryInstruction(opCode), ISupportsVolatilePrefix
@ -84,5 +99,10 @@ namespace ICSharpCode.Decompiler.IL @@ -84,5 +99,10 @@ namespace ICSharpCode.Decompiler.IL
Right.WriteTo(output);
output.Write(')');
}
public override InstructionFlags Flags
{
get { return InstructionFlags.SideEffects | InstructionFlags.MayThrow | Left.Flags | Right.Flags; }
}
}
}

11
ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs

@ -45,11 +45,22 @@ namespace ICSharpCode.Decompiler.IL @@ -45,11 +45,22 @@ namespace ICSharpCode.Decompiler.IL
get { return true; }
}
public abstract InstructionFlags Flags { get; }
public virtual void WriteTo(ITextOutput output)
{
output.Write(OpCode);
}
/// <summary>
/// Attempts inlining from the instruction stack into this instruction.
/// </summary>
/// <param name="flagsBefore">Combined instruction flags of the instructions
/// that the instructions getting inlined would get moved over.</param>
/// <param name="instructionStack">The instruction stack.</param>
/// <param name="finished">Receives 'true' if all open 'pop' or 'peek' placeholders were inlined into; false otherwise.</param>
internal abstract ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished);
public abstract void TransformChildren(Func<ILInstruction, ILInstruction> transformFunc);
}
}

10
ICSharpCode.Decompiler/IL/Instructions/MemoryInstruction.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using Mono.Cecil;
using ICSharpCode.Decompiler.Disassembler;
using Mono.Cecil;
using System;
using System.Collections.Generic;
using System.Linq;
@ -36,10 +37,15 @@ namespace ICSharpCode.Decompiler.IL @@ -36,10 +37,15 @@ namespace ICSharpCode.Decompiler.IL
output.Write("unaligned " + UnalignedPrefix + ".");
output.Write(OpCode);
output.Write(' ');
Disassembler.DisassemblerHelpers.WriteOperand(output, TypeReference);
TypeReference.WriteTo(output);
output.Write('(');
Operand.WriteTo(output);
output.Write(')');
}
public override InstructionFlags Flags
{
get { return Operand.Flags | InstructionFlags.SideEffects; }
}
}
}

8
ICSharpCode.Decompiler/IL/Instructions/OpCode.cs

@ -226,5 +226,13 @@ namespace ICSharpCode.Decompiler.IL @@ -226,5 +226,13 @@ namespace ICSharpCode.Decompiler.IL
UnboxAny,
NewObj,
Throw,
/// <summary>
/// A block of IL instructions. <see cref="IL.Block"/>
/// </summary>
Block,
/// <summary>
/// A container of IL blocks. <see cref="IL.BlockContainer"/>
/// </summary>
BlockContainer,
}
}

74
ICSharpCode.Decompiler/IL/Instructions/SimpleInstruction.cs

@ -16,39 +16,94 @@ namespace ICSharpCode.Decompiler.IL @@ -16,39 +16,94 @@ namespace ICSharpCode.Decompiler.IL
public override void TransformChildren(Func<ILInstruction, ILInstruction> transformFunc)
{
}
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished)
{
finished = true; // Nothing to do, since we don't have arguments.
return this;
}
}
class Nop() : SimpleInstruction(OpCode.Nop)
{
public override bool NoResult { get { return true; } }
public override InstructionFlags Flags
{
get { return InstructionFlags.None; }
}
}
class Pop() : SimpleInstruction(OpCode.Pop)
{
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished)
{
if (instructionStack.Count > 0 && SemanticHelper.MayReorder(flagsBefore, instructionStack.Peek().Flags)) {
finished = true;
return instructionStack.Pop();
}
finished = false;
return this;
}
public override InstructionFlags Flags
{
get { return InstructionFlags.MayPop; }
}
}
class Peek() : SimpleInstruction(OpCode.Peek)
{
public override bool IsPeeking { get { return true; } }
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished)
{
finished = false;
return this;
}
public override InstructionFlags Flags
{
get { return InstructionFlags.MayPeek; }
}
}
class Ckfinite() : SimpleInstruction(OpCode.Ckfinite)
{
public override bool IsPeeking { get { return true; } }
public override bool NoResult { get { return true; } }
public override InstructionFlags Flags
{
get { return InstructionFlags.MayPeek | InstructionFlags.MayThrow; }
}
}
class Arglist() : SimpleInstruction(OpCode.Arglist)
{
public override InstructionFlags Flags
{
get { return InstructionFlags.None; }
}
}
class DebugBreak() : SimpleInstruction(OpCode.DebugBreak)
{
public override bool NoResult { get { return true; } }
public override InstructionFlags Flags
{
get { return InstructionFlags.SideEffects; }
}
}
class ConstantString(public readonly string Value) : SimpleInstruction(OpCode.LdStr)
{
public override InstructionFlags Flags
{
get { return InstructionFlags.None; }
}
public override void WriteTo(ITextOutput output)
{
output.Write("ldstr ");
@ -58,6 +113,11 @@ namespace ICSharpCode.Decompiler.IL @@ -58,6 +113,11 @@ namespace ICSharpCode.Decompiler.IL
class ConstantI4(public readonly int Value) : SimpleInstruction(OpCode.LdcI4)
{
public override InstructionFlags Flags
{
get { return InstructionFlags.None; }
}
public override void WriteTo(ITextOutput output)
{
output.Write("ldc.i4 ");
@ -67,6 +127,11 @@ namespace ICSharpCode.Decompiler.IL @@ -67,6 +127,11 @@ namespace ICSharpCode.Decompiler.IL
class ConstantI8(public readonly long Value) : SimpleInstruction(OpCode.LdcI8)
{
public override InstructionFlags Flags
{
get { return InstructionFlags.None; }
}
public override void WriteTo(ITextOutput output)
{
output.Write("ldc.i8 ");
@ -76,6 +141,11 @@ namespace ICSharpCode.Decompiler.IL @@ -76,6 +141,11 @@ namespace ICSharpCode.Decompiler.IL
class ConstantFloat(public readonly double Value) : SimpleInstruction(OpCode.LdcF)
{
public override InstructionFlags Flags
{
get { return InstructionFlags.None; }
}
public override void WriteTo(ITextOutput output)
{
output.Write("ldc.f ");
@ -85,5 +155,9 @@ namespace ICSharpCode.Decompiler.IL @@ -85,5 +155,9 @@ namespace ICSharpCode.Decompiler.IL
class ConstantNull() : SimpleInstruction(OpCode.LdNull)
{
public override InstructionFlags Flags
{
get { return InstructionFlags.None; }
}
}
}

35
ICSharpCode.Decompiler/IL/Instructions/UnaryInstruction.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using Mono.Cecil;
using ICSharpCode.Decompiler.Disassembler;
using Mono.Cecil;
using System;
using System.Collections.Generic;
using System.Linq;
@ -25,15 +26,30 @@ namespace ICSharpCode.Decompiler.IL @@ -25,15 +26,30 @@ namespace ICSharpCode.Decompiler.IL
{
Operand = transformFunc(Operand);
}
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished)
{
Operand = Operand.Inline(flagsBefore, instructionStack, out finished);
return this;
}
}
class VoidInstruction() : UnaryInstruction(OpCode.Void)
{
public override bool NoResult { get { return true; } }
public override InstructionFlags Flags
{
get { return Operand.Flags; }
}
}
class LogicNotInstruction() : UnaryInstruction(OpCode.LogicNot)
{
public override InstructionFlags Flags
{
get { return Operand.Flags; }
}
}
class UnaryNumericInstruction(OpCode opCode, StackType opType) : UnaryInstruction(opCode)
@ -49,6 +65,11 @@ namespace ICSharpCode.Decompiler.IL @@ -49,6 +65,11 @@ namespace ICSharpCode.Decompiler.IL
Operand.WriteTo(output);
output.Write(')');
}
public override InstructionFlags Flags
{
get { return Operand.Flags; }
}
}
class IsInst(public readonly TypeReference Type) : UnaryInstruction(OpCode.IsInst)
@ -57,11 +78,16 @@ namespace ICSharpCode.Decompiler.IL @@ -57,11 +78,16 @@ namespace ICSharpCode.Decompiler.IL
{
output.Write(OpCode);
output.Write(' ');
Disassembler.DisassemblerHelpers.WriteOperand(output, Type);
Type.WriteTo(output);
output.Write('(');
Operand.WriteTo(output);
output.Write(')');
}
public override InstructionFlags Flags
{
get { return Operand.Flags; }
}
}
class ConvInstruction(
@ -80,6 +106,11 @@ namespace ICSharpCode.Decompiler.IL @@ -80,6 +106,11 @@ namespace ICSharpCode.Decompiler.IL
Operand.WriteTo(output);
output.Write(')');
}
public override InstructionFlags Flags
{
get { return Operand.Flags | InstructionFlags.MayThrow; }
}
}
}

18
ICSharpCode.Decompiler/IL/Instructions/VarInstructions.cs

@ -12,6 +12,11 @@ namespace ICSharpCode.Decompiler.IL @@ -12,6 +12,11 @@ namespace ICSharpCode.Decompiler.IL
{
Variable.WriteTo(output);
}
public override InstructionFlags Flags
{
get { return InstructionFlags.MayReadLocals; }
}
}
class LdLoca(public readonly ILVariable Variable) : SimpleInstruction(OpCode.LdLoca)
@ -21,6 +26,14 @@ namespace ICSharpCode.Decompiler.IL @@ -21,6 +26,14 @@ namespace ICSharpCode.Decompiler.IL
output.Write("ldloca ");
Variable.WriteTo(output);
}
public override InstructionFlags Flags
{
get {
// the address of a local can be considered to be a constant
return InstructionFlags.None;
}
}
}
class StLoc(public readonly ILVariable Variable) : UnaryInstruction(OpCode.StLoc)
@ -31,5 +44,10 @@ namespace ICSharpCode.Decompiler.IL @@ -31,5 +44,10 @@ namespace ICSharpCode.Decompiler.IL
output.Write(" = ");
Operand.WriteTo(output);
}
public override InstructionFlags Flags
{
get { return InstructionFlags.MayWriteLocals | Operand.Flags; }
}
}
}

30
ICSharpCode.Decompiler/IL/SemanticHelper.cs

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

2
ILSpy.sln

@ -6,7 +6,7 @@ MinimumVisualStudioVersion = 10.0.40219.1 @@ -6,7 +6,7 @@ MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{F45DB999-7E72-4000-B5AD-3A7B485A0896}"
ProjectSection(SolutionItems) = preProject
doc\Command Line.txt = doc\Command Line.txt
ILAst.txt = ILAst.txt
doc\ILAst.txt = doc\ILAst.txt
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ILSpy", "ILSpy\ILSpy.csproj", "{1E85EFF9-E370-4683-83E4-8A3D063FF791}"

19
ILSpy/Languages/ILAstLanguage.cs

@ -82,6 +82,8 @@ namespace ICSharpCode.ILSpy @@ -82,6 +82,8 @@ namespace ICSharpCode.ILSpy
internal static IEnumerable<ILAstLanguage> GetDebugLanguages()
{
yield return new TypedIL();
yield return new BlockIL(false);
yield return new BlockIL(true);
//yield return new ILAstLanguage { name = "ILAst (unoptimized)", inlineVariables = false };
//string nextName = "ILAst (variable splitting)";
//foreach (ILAstOptimizationStep step in Enum.GetValues(typeof(ILAstOptimizationStep))) {
@ -110,7 +112,8 @@ namespace ICSharpCode.ILSpy @@ -110,7 +112,8 @@ namespace ICSharpCode.ILSpy
base.DecompileMethod(method, output, options);
new ReflectionDisassembler(output, false, options.CancellationToken).DisassembleMethodHeader(method);
output.WriteLine();
}
output.WriteLine();
}
class TypedIL() : ILAstLanguage("Typed IL")
{
@ -123,6 +126,18 @@ namespace ICSharpCode.ILSpy @@ -123,6 +126,18 @@ namespace ICSharpCode.ILSpy
reader.WriteTypedIL(output);
}
}
}
class BlockIL(private bool instructionInlining) : ILAstLanguage(instructionInlining ? "ILAst (blocks+inlining)" : "ILAst (blocks)")
{
public override void DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options)
{
base.DecompileMethod(method, output, options);
if (!method.HasBody)
return;
ILReader reader = new ILReader(method.Body, options.CancellationToken);
reader.WriteBlocks(output, instructionInlining);
}
}
}
#endif
}

85
doc/ILAst.txt

@ -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…
Cancel
Save