Browse Source

Generalize inlining, and make it usable for symbolic phase-1 execution.

pull/728/head
Daniel Grunwald 10 years ago
parent
commit
2dd5a38d05
  1. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  2. 55
      ICSharpCode.Decompiler/IL/IInlineContext.cs
  3. 4
      ICSharpCode.Decompiler/IL/InstructionFlags.cs
  4. 56
      ICSharpCode.Decompiler/IL/Instructions.cs
  5. 11
      ICSharpCode.Decompiler/IL/Instructions.tt
  6. 24
      ICSharpCode.Decompiler/IL/Instructions/Block.cs
  7. 4
      ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs
  8. 7
      ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs
  9. 3
      ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs
  10. 16
      ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs
  11. 19
      ICSharpCode.Decompiler/IL/Instructions/IfInstruction.cs
  12. 6
      ICSharpCode.Decompiler/IL/Instructions/Return.cs
  13. 18
      ICSharpCode.Decompiler/IL/Instructions/SimpleInstruction.cs
  14. 17
      ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs
  15. 56
      ICSharpCode.Decompiler/IL/TransformingVisitor.cs
  16. 3
      doc/ILAst.txt

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -100,6 +100,7 @@
<Compile Include="IL\Instructions\SimpleInstruction.cs" /> <Compile Include="IL\Instructions\SimpleInstruction.cs" />
<Compile Include="IL\Instructions\TryInstruction.cs" /> <Compile Include="IL\Instructions\TryInstruction.cs" />
<Compile Include="IL\Instructions\UnaryInstruction.cs" /> <Compile Include="IL\Instructions\UnaryInstruction.cs" />
<Compile Include="IL\IInlineContext.cs" />
<Compile Include="IL\NRTypeExtensions.cs" /> <Compile Include="IL\NRTypeExtensions.cs" />
<Compile Include="IL\TransformingVisitor.cs" /> <Compile Include="IL\TransformingVisitor.cs" />
<Compile Include="TypesHierarchyHelpers.cs" /> <Compile Include="TypesHierarchyHelpers.cs" />

55
ICSharpCode.Decompiler/IL/IInlineContext.cs

@ -0,0 +1,55 @@
// 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;
namespace ICSharpCode.Decompiler.IL
{
/// <summary>
/// Execution context for phase 1: replace pop and peek instructions with evaluation stack values.
/// </summary>
interface IInlineContext
{
/// <summary>
/// Peeks at the top value on the evaluation stack, returning an instruction that represents
/// that value.
/// <see cref="ILInstruction.Inline"/> will replace <see cref="Peek"/> instructions
/// with the value returned by this function.
/// </summary>
/// <param name="flagsBefore">Combined instruction flags of the instructions
/// that the instruction getting inlined would get moved over.</param>
/// <remarks>
/// This method may return <c>null</c> when the evaluation stack is empty or the contents
/// are unknown. In this case, the peek instruction will not be replaced.
/// </remarks>
ILInstruction Peek(InstructionFlags flagsBefore);
/// <summary>
/// Pops the top value on the evaluation stack, returning an instruction that represents
/// that value.
/// <see cref="ILInstruction.Inline"/> will replace <see cref="Pop"/> instructions
/// with the value returned by this function.
/// </summary>
/// <param name="flagsBefore">Combined instruction flags of the instructions
/// that the instruction getting inlined would get moved over.</param>
/// <remarks>
/// This method may return <c>null</c> when the evaluation stack is empty or the contents
/// are unknown. In this case, the pop instruction will not be replaced.
/// </remarks>
ILInstruction Pop(InstructionFlags flagsBefore);
}
}

4
ICSharpCode.Decompiler/IL/InstructionFlags.cs

@ -64,6 +64,10 @@ namespace ICSharpCode.Decompiler.IL
/// The instruction may have side effects, such as accessing heap memory, /// The instruction may have side effects, such as accessing heap memory,
/// performing system calls, writing to local variables through pointers, etc. /// performing system calls, writing to local variables through pointers, etc.
/// </summary> /// </summary>
/// <remarks>
/// Throwing an exception or directly writing to local variables or the evaluation stack
/// is not considered a side effect, and is modeled by separate flags.
/// </remarks>
SideEffect = 0x40, SideEffect = 0x40,
/// <summary> /// <summary>

56
ICSharpCode.Decompiler/IL/Instructions.cs

@ -196,9 +196,9 @@ namespace ICSharpCode.Decompiler.IL
{ {
this.Argument = this.argument.AcceptVisitor(visitor); this.Argument = this.argument.AcceptVisitor(visitor);
} }
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
this.Argument = this.argument.Inline(flagsBefore, instructionStack, out finished); this.Argument = this.argument.Inline(flagsBefore, context);
return this; return this;
} }
protected override InstructionFlags ComputeFlags() protected override InstructionFlags ComputeFlags()
@ -249,11 +249,10 @@ namespace ICSharpCode.Decompiler.IL
this.Left = this.left.AcceptVisitor(visitor); this.Left = this.left.AcceptVisitor(visitor);
this.Right = this.right.AcceptVisitor(visitor); this.Right = this.right.AcceptVisitor(visitor);
} }
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
this.Right = this.right.Inline(flagsBefore | ((this.left.Flags) & ~(InstructionFlags.MayPeek | InstructionFlags.MayPop)), instructionStack, out finished); this.Right = this.right.Inline(flagsBefore | ((this.left.Flags) & ~(InstructionFlags.MayPeek | InstructionFlags.MayPop)), context);
if (finished) this.Left = this.left.Inline(flagsBefore, context);
this.Left = this.left.Inline(flagsBefore, instructionStack, out finished);
return this; return this;
} }
protected override InstructionFlags ComputeFlags() protected override InstructionFlags ComputeFlags()
@ -891,9 +890,9 @@ namespace ICSharpCode.Decompiler.IL
{ {
this.Value = this.value.AcceptVisitor(visitor); this.Value = this.value.AcceptVisitor(visitor);
} }
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
this.Value = this.value.Inline(flagsBefore, instructionStack, out finished); this.Value = this.value.Inline(flagsBefore, context);
return this; return this;
} }
readonly ILVariable variable; readonly ILVariable variable;
@ -1170,9 +1169,9 @@ namespace ICSharpCode.Decompiler.IL
{ {
this.Target = this.target.AcceptVisitor(visitor); this.Target = this.target.AcceptVisitor(visitor);
} }
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
this.Target = this.target.Inline(flagsBefore, instructionStack, out finished); this.Target = this.target.Inline(flagsBefore, context);
return this; return this;
} }
/// <summary>Gets/Sets whether the memory access is volatile.</summary> /// <summary>Gets/Sets whether the memory access is volatile.</summary>
@ -1231,9 +1230,9 @@ namespace ICSharpCode.Decompiler.IL
{ {
this.Target = this.target.AcceptVisitor(visitor); this.Target = this.target.AcceptVisitor(visitor);
} }
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
this.Target = this.target.Inline(flagsBefore, instructionStack, out finished); this.Target = this.target.Inline(flagsBefore, context);
return this; return this;
} }
readonly IField field; readonly IField field;
@ -1295,11 +1294,10 @@ namespace ICSharpCode.Decompiler.IL
this.Target = this.target.AcceptVisitor(visitor); this.Target = this.target.AcceptVisitor(visitor);
this.Value = this.value.AcceptVisitor(visitor); this.Value = this.value.AcceptVisitor(visitor);
} }
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
this.Value = this.value.Inline(flagsBefore | ((this.target.Flags) & ~(InstructionFlags.MayPeek | InstructionFlags.MayPop)), instructionStack, out finished); this.Value = this.value.Inline(flagsBefore | ((this.target.Flags) & ~(InstructionFlags.MayPeek | InstructionFlags.MayPop)), context);
if (finished) this.Target = this.target.Inline(flagsBefore, context);
this.Target = this.target.Inline(flagsBefore, instructionStack, out finished);
return this; return this;
} }
/// <summary>Gets/Sets whether the memory access is volatile.</summary> /// <summary>Gets/Sets whether the memory access is volatile.</summary>
@ -1418,9 +1416,9 @@ namespace ICSharpCode.Decompiler.IL
{ {
this.Value = this.value.AcceptVisitor(visitor); this.Value = this.value.AcceptVisitor(visitor);
} }
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
this.Value = this.value.Inline(flagsBefore, instructionStack, out finished); this.Value = this.value.Inline(flagsBefore, context);
return this; return this;
} }
/// <summary>Gets/Sets whether the memory access is volatile.</summary> /// <summary>Gets/Sets whether the memory access is volatile.</summary>
@ -1535,9 +1533,9 @@ namespace ICSharpCode.Decompiler.IL
{ {
this.Target = this.target.AcceptVisitor(visitor); this.Target = this.target.AcceptVisitor(visitor);
} }
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
this.Target = this.target.Inline(flagsBefore, instructionStack, out finished); this.Target = this.target.Inline(flagsBefore, context);
return this; return this;
} }
readonly IType type; readonly IType type;
@ -1607,11 +1605,10 @@ namespace ICSharpCode.Decompiler.IL
this.Target = this.target.AcceptVisitor(visitor); this.Target = this.target.AcceptVisitor(visitor);
this.Value = this.value.AcceptVisitor(visitor); this.Value = this.value.AcceptVisitor(visitor);
} }
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
this.Value = this.value.Inline(flagsBefore | ((this.target.Flags) & ~(InstructionFlags.MayPeek | InstructionFlags.MayPop)), instructionStack, out finished); this.Value = this.value.Inline(flagsBefore | ((this.target.Flags) & ~(InstructionFlags.MayPeek | InstructionFlags.MayPop)), context);
if (finished) this.Target = this.target.Inline(flagsBefore, context);
this.Target = this.target.Inline(flagsBefore, instructionStack, out finished);
return this; return this;
} }
readonly IType type; readonly IType type;
@ -1880,9 +1877,9 @@ namespace ICSharpCode.Decompiler.IL
{ {
this.Target = this.target.AcceptVisitor(visitor); this.Target = this.target.AcceptVisitor(visitor);
} }
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
this.Target = this.target.Inline(flagsBefore, instructionStack, out finished); this.Target = this.target.Inline(flagsBefore, context);
return this; return this;
} }
public override StackType ResultType { get { return StackType.I; } } public override StackType ResultType { get { return StackType.I; } }
@ -1939,11 +1936,10 @@ namespace ICSharpCode.Decompiler.IL
this.Array = this.array.AcceptVisitor(visitor); this.Array = this.array.AcceptVisitor(visitor);
this.Index = this.index.AcceptVisitor(visitor); this.Index = this.index.AcceptVisitor(visitor);
} }
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
this.Index = this.index.Inline(flagsBefore | ((this.array.Flags) & ~(InstructionFlags.MayPeek | InstructionFlags.MayPop)), instructionStack, out finished); this.Index = this.index.Inline(flagsBefore | ((this.array.Flags) & ~(InstructionFlags.MayPeek | InstructionFlags.MayPop)), context);
if (finished) this.Array = this.array.Inline(flagsBefore, context);
this.Array = this.array.Inline(flagsBefore, instructionStack, out finished);
return this; return this;
} }
readonly IType type; readonly IType type;

11
ICSharpCode.Decompiler/IL/Instructions.tt

@ -590,15 +590,12 @@ namespace ICSharpCode.Decompiler.IL
opCode.Members.Add(b.ToString()); opCode.Members.Add(b.ToString());
if (generateInline) { if (generateInline) {
b = new StringBuilder(); b = new StringBuilder();
b.AppendLine("internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished)"); b.AppendLine("internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)");
b.AppendLine("{"); b.AppendLine("{");
for (int i = children.Length - 1; i >= 0; i--) { for (int i = children.Length - 1; i >= 0; i--) {
string arg = children[i].Name; string arg = children[i].Name;
if (i < children.Length - 1) { if (children[i].IsOptional) {
if (children[i].IsOptional) b.AppendLine("\tif (this." + arg + " != null)");
b.AppendLine("\tif (finished && this." + arg + " != null)");
else
b.AppendLine("\tif (finished)");
b.Append("\t"); b.Append("\t");
} }
b.Append("\tthis." + MakeName(arg) + " = this." + arg + ".Inline(flagsBefore"); b.Append("\tthis." + MakeName(arg) + " = this." + arg + ".Inline(flagsBefore");
@ -607,7 +604,7 @@ namespace ICSharpCode.Decompiler.IL
b.Append(string.Join(" | ", children.Take(i).Select(child2 => "this." + child2.Name + ".Flags"))); b.Append(string.Join(" | ", children.Take(i).Select(child2 => "this." + child2.Name + ".Flags")));
b.Append(") & ~(InstructionFlags.MayPeek | InstructionFlags.MayPop))"); b.Append(") & ~(InstructionFlags.MayPeek | InstructionFlags.MayPop))");
} }
b.AppendLine(", instructionStack, out finished);"); b.AppendLine(", context);");
} }
b.AppendLine("\treturn this;"); b.AppendLine("\treturn this;");
b.Append("}"); b.Append("}");

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

@ -41,15 +41,8 @@ namespace ICSharpCode.Decompiler.IL
/// } /// }
/// </code> /// </code>
/// <para> /// <para>
/// If the execution reaches the end of the block, one element is popped from the evaluation stack and /// If the execution reaches the end of the block, the block returns the result value of the last instruction.
/// is used as the return value of the block.
/// This means it is impossible for a block to return void: any block with ResultType void must
/// perform an unconditional branch!
/// </para> /// </para>
/// TODO: that seems like a bad idea for the purposes of goto-removal, I think we'll want
/// separate classes for void-blocks and inline-blocks.
/// Or maybe let blocks evaluate to the return value of their last instruction, and use a final pop()
/// instruction for inline-block semantics.
/// TODO: actually I think it's a good idea to implement a small ILAst interpreter /// TODO: actually I think it's a good idea to implement a small ILAst interpreter
/// public virtual ILInstruction Phase1(InterpreterState state); /// public virtual ILInstruction Phase1(InterpreterState state);
/// public virtual InterpreterResult ILInstruction Phase2(InterpreterState state); /// public virtual InterpreterResult ILInstruction Phase2(InterpreterState state);
@ -77,17 +70,13 @@ namespace ICSharpCode.Decompiler.IL
public override StackType ResultType { public override StackType ResultType {
get { get {
return StackType.Void; if (Instructions.Count == 0)
return StackType.Void;
else
return Instructions.Last().ResultType;
} }
} }
internal override void CheckInvariant()
{
base.CheckInvariant();
// if the end-point isn't unreachable, there's an implicit pop at the end of the block
Debug.Assert(this.HasFlag(InstructionFlags.EndPointUnreachable) || this.ResultType != StackType.Void);
}
/// <summary> /// <summary>
/// Gets the name of this block. /// Gets the name of this block.
/// </summary> /// </summary>
@ -156,10 +145,9 @@ namespace ICSharpCode.Decompiler.IL
return flags; return flags;
} }
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
// an inline block has no phase-1 effects, so we're immediately done with inlining // an inline block has no phase-1 effects, so we're immediately done with inlining
finished = true;
return this; return this;
} }
} }

4
ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs

@ -97,9 +97,9 @@ namespace ICSharpCode.Decompiler.IL
| (flagsInAllBlocks & InstructionFlags.EndPointUnreachable); | (flagsInAllBlocks & InstructionFlags.EndPointUnreachable);
} }
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
finished = false; // Blocks are phase-1 boundaries
return this; return this;
} }
} }

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

@ -112,18 +112,15 @@ namespace ICSharpCode.Decompiler.IL
output.Write(')'); output.Write(')');
} }
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
InstructionFlags operandFlags = InstructionFlags.None; InstructionFlags operandFlags = InstructionFlags.None;
for (int i = 0; i < Arguments.Count - 1; i++) { for (int i = 0; i < Arguments.Count - 1; i++) {
operandFlags |= Arguments[i].Flags; operandFlags |= Arguments[i].Flags;
} }
flagsBefore |= operandFlags & ~(InstructionFlags.MayPeek | InstructionFlags.MayPop); flagsBefore |= operandFlags & ~(InstructionFlags.MayPeek | InstructionFlags.MayPop);
finished = true;
for (int i = Arguments.Count - 1; i >= 0; i--) { for (int i = Arguments.Count - 1; i >= 0; i--) {
Arguments[i] = Arguments[i].Inline(flagsBefore, instructionStack, out finished); Arguments[i] = Arguments[i].Inline(flagsBefore, context);
if (!finished)
break;
} }
return this; return this;
} }

3
ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs

@ -61,10 +61,9 @@ namespace ICSharpCode.Decompiler.IL
return InstructionFlags.MayThrow; return InstructionFlags.MayThrow;
} }
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
// To the outside, lambda creation looks like a constant // To the outside, lambda creation looks like a constant
finished = true;
return this; return this;
} }
} }

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

@ -153,13 +153,21 @@ namespace ICSharpCode.Decompiler.IL
public abstract void TransformChildren(ILVisitor<ILInstruction> visitor); public abstract void TransformChildren(ILVisitor<ILInstruction> visitor);
/// <summary> /// <summary>
/// Attempts inlining from the instruction stack into this instruction. /// Attempts inlining from the inline context into this instruction.
/// </summary> /// </summary>
/// <param name="flagsBefore">Combined instruction flags of the instructions /// <param name="flagsBefore">Combined instruction flags of the instructions
/// that the instructions getting inlined would get moved over.</param> /// that the instructions getting inlined would get moved over.</param>
/// <param name="instructionStack">The instruction stack.</param> /// <param name="context">The inline context providing the values on the evaluation stack.</param>
/// <param name="finished">Receives 'true' if all open 'pop' or 'peek' placeholders were inlined into; false otherwise.</param> /// <returns>
internal abstract ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished); /// Returns the modified ILInstruction after inlining is complete.
/// Note that inlining modifies the AST in-place, so this method usually returns <c>this</c>
/// (unless <c>this</c> should be replaced by another node)
/// </returns>
/// <remarks>
/// Inlining from an inline context representing the actual evaluation stack
/// is equivalent to phase-1 execution of the instruction.
/// </remarks>
internal abstract ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context);
/// <summary> /// <summary>
/// Number of parents that refer to this instruction and are connected to the root. /// Number of parents that refer to this instruction and are connected to the root.

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

@ -22,6 +22,15 @@ using System.Diagnostics;
namespace ICSharpCode.Decompiler.IL namespace ICSharpCode.Decompiler.IL
{ {
/// <summary>If statement / conditional expression. <c>if (condition) trueExpr else falseExpr</c></summary>
/// <remarks>
/// The condition must return StackType.I4, use comparison instructions like Ceq to check if other types are non-zero.
/// Phase-1 execution of an IfInstruction consists of phase-1 execution of the condition.
/// Phase-2 execution of an IfInstruction will phase-2-execute the condition.
/// If the condition evaluates to a non-zero, the TrueInst is executed (both phase-1 and phase-2).
/// If the condition evaluates to zero, the FalseInst is executed (both phase-1 and phase-2).
/// The return value of the IfInstruction is the return value of the TrueInst or FalseInst.
/// </remarks>
partial class IfInstruction : ILInstruction partial class IfInstruction : ILInstruction
{ {
public IfInstruction(ILInstruction condition, ILInstruction trueInst, ILInstruction falseInst = null) : base(OpCode.IfInstruction) public IfInstruction(ILInstruction condition, ILInstruction trueInst, ILInstruction falseInst = null) : base(OpCode.IfInstruction)
@ -43,18 +52,16 @@ namespace ICSharpCode.Decompiler.IL
} }
} }
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
this.Condition = condition.Inline(flagsBefore, instructionStack, out finished); this.Condition = condition.Inline(flagsBefore, context);
// don't continue inlining if this instruction still contains peek/pop instructions // note: we skip TrueInst and FalseInst because there's a phase-1-boundary around them
if (HasFlag(InstructionFlags.MayPeek | InstructionFlags.MayPop))
finished = false;
return this; return this;
} }
protected override InstructionFlags ComputeFlags() protected override InstructionFlags ComputeFlags()
{ {
return condition.Flags | CombineFlags(trueInst.Flags, falseInst.Flags); return condition.Flags | Block.Phase1Boundary(CombineFlags(trueInst.Flags, falseInst.Flags));
} }
internal static InstructionFlags CombineFlags(InstructionFlags trueFlags, InstructionFlags falseFlags) internal static InstructionFlags CombineFlags(InstructionFlags trueFlags, InstructionFlags falseFlags)

6
ICSharpCode.Decompiler/IL/Instructions/Return.cs

@ -79,12 +79,10 @@ namespace ICSharpCode.Decompiler.IL
this.ReturnValue = returnValue.AcceptVisitor(visitor); this.ReturnValue = returnValue.AcceptVisitor(visitor);
} }
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
if (returnValue != null) if (returnValue != null)
this.ReturnValue = returnValue.Inline(flagsBefore, instructionStack, out finished); this.ReturnValue = returnValue.Inline(flagsBefore, context);
else
finished = true;
return this; return this;
} }
} }

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

@ -54,32 +54,26 @@ namespace ICSharpCode.Decompiler.IL
return InstructionFlags.None; return InstructionFlags.None;
} }
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
finished = true; // Nothing to do, since we don't have arguments. // Nothing to do, since we don't have arguments.
return this; return this;
} }
} }
partial class Pop partial class Pop
{ {
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
if (instructionStack.Count > 0 && SemanticHelper.MayReorder(flagsBefore, instructionStack.Peek().Flags)) { return context.Pop(flagsBefore) ?? this;
finished = true;
return instructionStack.Pop();
}
finished = false;
return this;
} }
} }
partial class Peek partial class Peek
{ {
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
finished = false; return context.Peek(flagsBefore) ?? this;
return this;
} }
} }
} }

17
ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs

@ -38,10 +38,11 @@ namespace ICSharpCode.Decompiler.IL
} }
} }
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
// Cannot inline into try instructions because moving code into the try block would change semantics // Inlining into exception-handling constructs would be madness.
finished = false; // To keep phase-1 execution semantics consistent with inlining, there's a
// phase-1-boundary around every try/catch/finally/fault block.
return this; return this;
} }
} }
@ -79,7 +80,7 @@ namespace ICSharpCode.Decompiler.IL
protected override InstructionFlags ComputeFlags() protected override InstructionFlags ComputeFlags()
{ {
var flags = TryBlock.Flags; var flags = Block.Phase1Boundary(TryBlock.Flags);
foreach (var handler in Handlers) foreach (var handler in Handlers)
flags = IfInstruction.CombineFlags(flags, handler.Flags); flags = IfInstruction.CombineFlags(flags, handler.Flags);
return flags; return flags;
@ -117,7 +118,7 @@ namespace ICSharpCode.Decompiler.IL
/// </summary> /// </summary>
partial class TryCatchHandler partial class TryCatchHandler
{ {
internal override ILInstruction Inline(InstructionFlags flagsBefore, Stack<ILInstruction> instructionStack, out bool finished) internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{ {
// should never happen as TryCatchHandler only appears within TryCatch instructions // should never happen as TryCatchHandler only appears within TryCatch instructions
throw new InvalidOperationException(); throw new InvalidOperationException();
@ -136,7 +137,7 @@ namespace ICSharpCode.Decompiler.IL
protected override InstructionFlags ComputeFlags() protected override InstructionFlags ComputeFlags()
{ {
return Block.Phase1Boundary(filter.Flags | body.Flags); return Block.Phase1Boundary(filter.Flags) | Block.Phase1Boundary(body.Flags);
} }
public override void WriteTo(ITextOutput output) public override void WriteTo(ITextOutput output)
@ -200,7 +201,7 @@ namespace ICSharpCode.Decompiler.IL
protected override InstructionFlags ComputeFlags() protected override InstructionFlags ComputeFlags()
{ {
// if the endpoint of either the try or the finally is unreachable, the endpoint of the try-finally will be unreachable // if the endpoint of either the try or the finally is unreachable, the endpoint of the try-finally will be unreachable
return TryBlock.Flags | finallyBlock.Flags; return Block.Phase1Boundary(TryBlock.Flags) | Block.Phase1Boundary(finallyBlock.Flags);
} }
public override IEnumerable<ILInstruction> Children { public override IEnumerable<ILInstruction> Children {
@ -248,7 +249,7 @@ namespace ICSharpCode.Decompiler.IL
protected override InstructionFlags ComputeFlags() protected override InstructionFlags ComputeFlags()
{ {
// The endpoint of the try-fault is unreachable only if both endpoints are unreachable // The endpoint of the try-fault is unreachable only if both endpoints are unreachable
return IfInstruction.CombineFlags(TryBlock.Flags, faultBlock.Flags); return IfInstruction.CombineFlags(Block.Phase1Boundary(TryBlock.Flags), Block.Phase1Boundary(faultBlock.Flags));
} }
public override IEnumerable<ILInstruction> Children { public override IEnumerable<ILInstruction> Children {

56
ICSharpCode.Decompiler/IL/TransformingVisitor.cs

@ -54,20 +54,52 @@ namespace ICSharpCode.Decompiler.IL
protected bool removeNops; protected bool removeNops;
sealed class InliningStack : Stack<ILInstruction>, IInlineContext
{
/// <summary>
/// Indicates whether inlining was success for at least one
/// peek or pop instruction.
/// </summary>
internal bool didInline;
/// <summary>
/// Indicates whether inlining encountered a peek or pop instruction
/// that could not be inlined.
/// </summary>
internal bool error;
ILInstruction IInlineContext.Peek(InstructionFlags flagsBefore)
{
error = true;
return null;
}
ILInstruction IInlineContext.Pop(InstructionFlags flagsBefore)
{
if (error)
return null;
if (base.Count > 0 && SemanticHelper.MayReorder(flagsBefore, base.Peek().Flags)) {
didInline = true;
return base.Pop();
}
error = true;
return null;
}
}
protected internal override ILInstruction VisitBlock(Block block) protected internal override ILInstruction VisitBlock(Block block)
{ {
Stack<ILInstruction> stack = new Stack<ILInstruction>(); var stack = new InliningStack();
List<ILInstruction> output = new List<ILInstruction>(); List<ILInstruction> output = new List<ILInstruction>();
for (int i = 0; i < block.Instructions.Count; i++) { for (int i = 0; i < block.Instructions.Count; i++) {
var inst = block.Instructions[i]; var inst = block.Instructions[i];
int stackCountBefore;
bool finished;
do { do {
inst = inst.AcceptVisitor(this); inst = inst.AcceptVisitor(this);
stackCountBefore = stack.Count; stack.didInline = false;
inst = inst.Inline(InstructionFlags.None, stack, out finished); stack.error = false;
} while (stack.Count != stackCountBefore); // repeat transformations when something was inlined inst = inst.Inline(InstructionFlags.None, stack);
if (inst.HasFlag(InstructionFlags.MayBranch) || !finished) { } while (stack.didInline); // repeat transformations when something was inlined
if (stack.error || inst.HasFlag(InstructionFlags.MayBranch)) {
// Values currently on the stack might be used on both sides of the branch, // Values currently on the stack might be used on both sides of the branch,
// so we can't inline them. // so we can't inline them.
// We also have to flush the stack if some occurrence of 'pop' or 'peek' remains in the current instruction. // We also have to flush the stack if some occurrence of 'pop' or 'peek' remains in the current instruction.
@ -76,9 +108,11 @@ namespace ICSharpCode.Decompiler.IL
if (inst.ResultType == StackType.Void) { if (inst.ResultType == StackType.Void) {
// We cannot directly push instructions onto the stack if they don't produce // We cannot directly push instructions onto the stack if they don't produce
// a result. // a result.
if (finished && stack.Count > 0) { if (!stack.error && stack.Count > 0) {
// Wrap the instruction on top of the stack into an inline block, // Wrap the instruction on top of the stack into an inline block,
// and append our void-typed instruction to the end of that block. // and append our void-typed instruction to the end of that block.
// TODO: I think this is wrong now that we changed the inline block semantics;
// we need to re-think how to build inline blocks.
var headInst = stack.Pop(); var headInst = stack.Pop();
var nestedBlock = headInst as Block ?? new Block { var nestedBlock = headInst as Block ?? new Block {
Instructions = { headInst }, Instructions = { headInst },
@ -126,8 +160,10 @@ namespace ICSharpCode.Decompiler.IL
// we can remove the container. // we can remove the container.
if (container.Blocks.Count == 1 && container.EntryPoint.IncomingEdgeCount == 1) { if (container.Blocks.Count == 1 && container.EntryPoint.IncomingEdgeCount == 1) {
// If the block has only one instruction, we can remove the block too // If the block has only one instruction, we can remove the block too
if (container.EntryPoint.Instructions.Count == 1) // (but only if this doesn't change the pop-order in the phase 1 evaluation of the parent block)
return container.EntryPoint.Instructions[0]; var instructions = container.EntryPoint.Instructions;
if (instructions.Count == 1 && !instructions[0].HasFlag(InstructionFlags.MayPeek | InstructionFlags.MayPop))
return instructions[0];
return container.EntryPoint; return container.EntryPoint;
} }
return container; return container;

3
doc/ILAst.txt

@ -62,7 +62,8 @@ For example, consider the ILAst for 'new List<int> { 1 }.Length':
call get_Length( call get_Length(
{ newobj List<int>() { newobj List<int>()
call Add(peek, ldc.i4 1) call Add(peek, ldc.i4 1)
}) // inline blocks evaluate to the value they pushed onto the stack pop
}) // inline blocks evaluate to the value of their last instruction
When evaluating the 'call get_Length' instruction, in phase 1 we cannot completely replace all 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. 'peek' and 'pop' instructions with values from the stack, because the List<int> object is not yet pushed to the stack.

Loading…
Cancel
Save