Browse Source

First version of variable inlining.

Added ILAst SlotInfo.
pull/728/head
Daniel Grunwald 10 years ago
parent
commit
1720fe5bba
  1. 3
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  2. 2
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  3. 65
      ICSharpCode.Decompiler/IL/ILReader.cs
  4. 14
      ICSharpCode.Decompiler/IL/ILVariable.cs
  5. 20
      ICSharpCode.Decompiler/IL/InstructionFlags.cs
  6. 416
      ICSharpCode.Decompiler/IL/Instructions.cs
  7. 70
      ICSharpCode.Decompiler/IL/Instructions.tt
  8. 47
      ICSharpCode.Decompiler/IL/Instructions/Block.cs
  9. 12
      ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs
  10. 20
      ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs
  11. 6
      ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs
  12. 45
      ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs
  13. 7
      ICSharpCode.Decompiler/IL/Instructions/IfInstruction.cs
  14. 20
      ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs
  15. 10
      ICSharpCode.Decompiler/IL/Instructions/Return.cs
  16. 32
      ICSharpCode.Decompiler/IL/Instructions/SimpleInstruction.cs
  17. 24
      ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs
  18. 53
      ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs
  19. 24
      ICSharpCode.Decompiler/IL/SemanticHelper.cs
  20. 44
      ICSharpCode.Decompiler/IL/SlotInfo.cs
  21. 318
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  22. 4
      ICSharpCode.Decompiler/IL/Transforms/OptimizingTransform.cs
  23. 5
      ICSharpCode.Decompiler/IL/Transforms/TransformingVisitor.cs
  24. 1
      ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj
  25. 62
      ICSharpCode.Decompiler/Tests/TestCases/ValueTypeCall.cs
  26. 6
      ICSharpCode.Decompiler/Tests/TestRunner.cs

3
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -44,6 +44,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -44,6 +44,7 @@ namespace ICSharpCode.Decompiler.CSharp
new OptimizingTransform(),
new LoopDetection(),
new ControlFlowSimplification(),
new ILInlining(),
new TransformingVisitor()
};
@ -269,6 +270,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -269,6 +270,8 @@ namespace ICSharpCode.Decompiler.CSharp
// insert variables at start of body
Statement prevVarDecl = null;
foreach (var v in function.Variables) {
if (v.LoadCount == 0 && v.StoreCount == 0 && v.AddressCount == 0)
continue;
if (v.Kind == VariableKind.Local || v.Kind == VariableKind.StackSlot) {
var type = typeSystemAstBuilder.ConvertType(v.Type);
var varDecl = new VariableDeclarationStatement(type, v.Name);

2
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -105,8 +105,10 @@ @@ -105,8 +105,10 @@
<Compile Include="IL\Instructions\UnaryInstruction.cs" />
<Compile Include="IL\IInlineContext.cs" />
<Compile Include="IL\NRTypeExtensions.cs" />
<Compile Include="IL\SlotInfo.cs" />
<Compile Include="IL\Transforms\ControlFlowSimplification.cs" />
<Compile Include="IL\Transforms\IILTransform.cs" />
<Compile Include="IL\Transforms\ILInlining.cs" />
<Compile Include="IL\Transforms\LoopDetection.cs" />
<Compile Include="IL\Transforms\OptimizingTransform.cs" />
<Compile Include="IL\Transforms\TransformingVisitor.cs" />

65
ICSharpCode.Decompiler/IL/ILReader.cs

@ -150,7 +150,8 @@ namespace ICSharpCode.Decompiler.IL @@ -150,7 +150,8 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>
/// Warn when invalid IL is detected.
/// ILSpy should be able to handle invalid IL; but this method can be helpful for debugging the ILReader, as this method should not get called when processing valid IL.
/// ILSpy should be able to handle invalid IL; but this method can be helpful for debugging the ILReader,
/// as this method should not get called when processing valid IL.
/// </summary>
void Warn(string message)
{
@ -207,6 +208,7 @@ namespace ICSharpCode.Decompiler.IL @@ -207,6 +208,7 @@ namespace ICSharpCode.Decompiler.IL
Warn("Unknown result type (might be due to invalid IL)");
decodedInstruction.CheckInvariant();
decodedInstruction.ILRange = new Interval(start, reader.Position);
UnpackPush(decodedInstruction).ILRange = decodedInstruction.ILRange;
instructionBuilder.Add(decodedInstruction);
if (decodedInstruction.HasFlag(InstructionFlags.EndPointUnreachable)) {
if (!stackByOffset.TryGetValue(reader.Position, out currentStack)) {
@ -269,6 +271,16 @@ namespace ICSharpCode.Decompiler.IL @@ -269,6 +271,16 @@ namespace ICSharpCode.Decompiler.IL
return function;
}
static ILInstruction UnpackPush(ILInstruction inst)
{
ILVariable v;
ILInstruction inner;
if (inst.MatchStLoc(out v, out inner) && v.Kind == VariableKind.StackSlot)
return inner;
else
return inst;
}
ILInstruction Neg()
{
switch (PeekStackType()) {
@ -280,7 +292,7 @@ namespace ICSharpCode.Decompiler.IL @@ -280,7 +292,7 @@ namespace ICSharpCode.Decompiler.IL
case StackType.F:
return Push(new Sub(new LdcF(0), Pop(), checkForOverflow: false, sign: Sign.None));
default:
Warn("Unsupported input type for neg: ");
Warn("Unsupported input type for neg.");
goto case StackType.I4;
}
}
@ -669,14 +681,7 @@ namespace ICSharpCode.Decompiler.IL @@ -669,14 +681,7 @@ namespace ICSharpCode.Decompiler.IL
case ILOpCode.Stsfld:
return new StsFld(Pop(), ReadAndDecodeFieldReference());
case ILOpCode.Ldtoken:
var memberReference = ReadAndDecodeMetadataToken() as MemberReference;
if (memberReference is TypeReference)
return new LdTypeToken(typeSystem.Resolve((TypeReference)memberReference));
if (memberReference is FieldReference)
return new LdMemberToken(typeSystem.Resolve((FieldReference)memberReference));
if (memberReference is MethodReference)
return new LdMemberToken(typeSystem.Resolve((MethodReference)memberReference));
throw new NotImplementedException();
return Push(LdToken(ReadAndDecodeMetadataToken()));
case ILOpCode.Ldvirtftn:
return Push(new LdVirtFtn(Pop(), ReadAndDecodeMethodReference()));
case ILOpCode.Mkrefany:
@ -690,7 +695,7 @@ namespace ICSharpCode.Decompiler.IL @@ -690,7 +695,7 @@ namespace ICSharpCode.Decompiler.IL
case ILOpCode.Rethrow:
return new Rethrow();
case ILOpCode.Sizeof:
return new SizeOf(ReadAndDecodeTypeReference());
return Push(new SizeOf(ReadAndDecodeTypeReference()));
case ILOpCode.Stelem:
return StElem(ReadAndDecodeTypeReference());
case ILOpCode.Stelem_I1:
@ -780,19 +785,20 @@ namespace ICSharpCode.Decompiler.IL @@ -780,19 +785,20 @@ namespace ICSharpCode.Decompiler.IL
ILInstruction Push(ILInstruction inst)
{
Debug.Assert(inst.ResultType != StackType.Void);
var v = new ILVariable(VariableKind.StackSlot, inst.ResultType, inst.ILRange.Start);
IType type = compilation.FindType(inst.ResultType.ToKnownTypeCode());
var v = new ILVariable(VariableKind.StackSlot, type, inst.ResultType, inst.ILRange.Start);
v.Name = "S_" + inst.ILRange.Start.ToString("x4");
currentStack = currentStack.Push(v);
return new StLoc(v, inst);
}
IL.LdLoc Peek()
LdLoc Peek()
{
// TODO: handle stack underflow?
return new LdLoc(currentStack.Peek());
}
IL.LdLoc Pop()
LdLoc Pop()
{
// TODO: handle stack underflow?
ILVariable v;
@ -803,9 +809,9 @@ namespace ICSharpCode.Decompiler.IL @@ -803,9 +809,9 @@ namespace ICSharpCode.Decompiler.IL
private ILInstruction Return()
{
if (body.Method.ReturnType.GetStackType() == StackType.Void)
return new ICSharpCode.Decompiler.IL.Return();
return new IL.Return();
else
return new ICSharpCode.Decompiler.IL.Return(Pop());
return new IL.Return(Pop());
}
private ILInstruction DecodeLdstr()
@ -859,25 +865,29 @@ namespace ICSharpCode.Decompiler.IL @@ -859,25 +865,29 @@ namespace ICSharpCode.Decompiler.IL
ILInstruction InitObj(ILInstruction target, IType type)
{
return new Void(new StObj(target, new DefaultValue(type), type));
return new StObj(target, new DefaultValue(type), type);
}
private ILInstruction DecodeConstrainedCall()
{
var typeRef = ReadAndDecodeTypeReference();
var inst = DecodeInstruction();
var call = inst as CallInstruction;
var call = UnpackPush(inst) as CallInstruction;
if (call != null)
call.ConstrainedTo = typeRef;
else
Warn("Ignored invalid 'constrained' prefix");
return inst;
}
private ILInstruction DecodeTailCall()
{
var inst = DecodeInstruction();
var call = inst as CallInstruction;
var call = UnpackPush(inst) as CallInstruction;
if (call != null)
call.IsTail = true;
else
Warn("Ignored invalid 'tail' prefix");
return inst;
}
@ -885,7 +895,7 @@ namespace ICSharpCode.Decompiler.IL @@ -885,7 +895,7 @@ namespace ICSharpCode.Decompiler.IL
{
byte alignment = reader.ReadByte();
var inst = DecodeInstruction();
var sup = inst as ISupportsUnalignedPrefix;
var sup = UnpackPush(inst) as ISupportsUnalignedPrefix;
if (sup != null)
sup.UnalignedPrefix = alignment;
else
@ -896,7 +906,7 @@ namespace ICSharpCode.Decompiler.IL @@ -896,7 +906,7 @@ namespace ICSharpCode.Decompiler.IL
private ILInstruction DecodeVolatile()
{
var inst = DecodeInstruction();
var svp = inst as ISupportsVolatilePrefix;
var svp = UnpackPush(inst) as ISupportsVolatilePrefix;
if (svp != null)
svp.IsVolatile = true;
else
@ -907,7 +917,7 @@ namespace ICSharpCode.Decompiler.IL @@ -907,7 +917,7 @@ namespace ICSharpCode.Decompiler.IL
private ILInstruction DecodeReadonly()
{
var inst = DecodeInstruction();
var ldelema = inst as LdElema;
var ldelema = UnpackPush(inst) as LdElema;
if (ldelema != null)
ldelema.IsReadOnly = true;
else
@ -1043,5 +1053,16 @@ namespace ICSharpCode.Decompiler.IL @@ -1043,5 +1053,16 @@ namespace ICSharpCode.Decompiler.IL
var left = Pop();
return Push(BinaryNumericInstruction.Create(opCode, left, right, checkForOverflow, sign));
}
ILInstruction LdToken(IMetadataTokenProvider token)
{
if (token is TypeReference)
return new LdTypeToken(typeSystem.Resolve((TypeReference)token));
if (token is FieldReference)
return new LdMemberToken(typeSystem.Resolve((FieldReference)token));
if (token is MethodReference)
return new LdMemberToken(typeSystem.Resolve((MethodReference)token));
throw new NotImplementedException();
}
}
}

14
ICSharpCode.Decompiler/IL/ILVariable.cs

@ -29,6 +29,10 @@ namespace ICSharpCode.Decompiler.IL @@ -29,6 +29,10 @@ namespace ICSharpCode.Decompiler.IL
/// </summary>
Local,
/// <summary>
/// A pinned local variable
/// </summary>
PinnedLocal,
/// <summary>
/// A parameter.
/// </summary>
Parameter,
@ -96,13 +100,11 @@ namespace ICSharpCode.Decompiler.IL @@ -96,13 +100,11 @@ namespace ICSharpCode.Decompiler.IL
this.Index = index;
}
public ILVariable(VariableKind kind, StackType type, int index)
public ILVariable(VariableKind kind, IType type, StackType stackType, int index)
{
if (type == null)
throw new ArgumentNullException("type");
this.Kind = kind;
this.Type = SpecialType.UnknownType;
this.StackType = type;
this.Type = type;
this.StackType = stackType;
this.Index = index;
}
@ -115,6 +117,8 @@ namespace ICSharpCode.Decompiler.IL @@ -115,6 +117,8 @@ namespace ICSharpCode.Decompiler.IL
{
output.WriteDefinition(this.Name, this, isLocal: true);
output.Write(" : ");
if (Kind == VariableKind.PinnedLocal)
output.Write("pinned ");
Type.WriteTo(output);
output.Write("({0} ldloc, {1} ldloca, {2} stloc)", LoadCount, AddressCount, StoreCount);
}

20
ICSharpCode.Decompiler/IL/InstructionFlags.cs

@ -28,26 +28,6 @@ namespace ICSharpCode.Decompiler.IL @@ -28,26 +28,6 @@ namespace ICSharpCode.Decompiler.IL
public enum InstructionFlags
{
None = 0,
/// <summary>
/// Phase-1 execution of this instruction may pop from the evaluation stack.
/// </summary>
MayPop = 0x01,
/// <summary>
/// Phase-1 execution of this instruction may peek at the top-most element of the evaluation stack.
/// If MayPop is also set, this flag refers to the top-most element after an arbitrary number of pops.
/// </summary>
MayPeek = 0x02,
/// <summary>
/// Phase-2 execution of this instruction may read the evaluation stack.
/// This is not set for instructions that access the stack only in phase-1.
/// </summary>
MayReadEvaluationStack = 0x04,
/// <summary>
/// Phase-2 execution of this instruction may modify the evaluation stack.
/// This is not set for instructions that access the stack only in phase-1.
/// </summary>
MayWriteEvaluationStack = 0x08,
/// <summary>
/// The instruction may read from local variables.
/// </summary>

416
ICSharpCode.Decompiler/IL/Instructions.cs

@ -102,6 +102,8 @@ namespace ICSharpCode.Decompiler.IL @@ -102,6 +102,8 @@ namespace ICSharpCode.Decompiler.IL
LdLoca,
/// <summary>Stores a value into a local variable. (starg/stloc)</summary>
StLoc,
/// <summary>Stores the value into an anonymous temporary variable, and returns the address of that variable.</summary>
AddressOf,
/// <summary>Loads a constant string.</summary>
LdStr,
/// <summary>Loads a constant 32-bit integer.</summary>
@ -172,6 +174,44 @@ namespace ICSharpCode.Decompiler.IL @@ -172,6 +174,44 @@ namespace ICSharpCode.Decompiler.IL
LdElema,
}
/// <summary>Instruction without any arguments</summary>
public abstract partial class SimpleInstruction : ILInstruction
{
protected SimpleInstruction(OpCode opCode) : base(opCode)
{
}
protected sealed override int GetChildCount()
{
return 0;
}
protected sealed override ILInstruction GetChild(int index)
{
switch (index) {
default:
throw new IndexOutOfRangeException();
}
}
protected sealed override void SetChild(int index, ILInstruction value)
{
switch (index) {
default:
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (SimpleInstruction)ShallowClone();
return clone;
}
}
/// <summary>Instruction with a single argument</summary>
public abstract partial class UnaryInstruction : ILInstruction
{
@ -179,11 +219,12 @@ namespace ICSharpCode.Decompiler.IL @@ -179,11 +219,12 @@ namespace ICSharpCode.Decompiler.IL
{
this.Argument = argument;
}
public static readonly SlotInfo ArgumentSlot = new SlotInfo("Argument", canInlineInto: true);
ILInstruction argument;
public ILInstruction Argument {
get { return this.argument; }
set {
ValidateArgument(value);
ValidateChild(value);
SetChildInstruction(ref this.argument, value, 0);
}
}
@ -210,17 +251,21 @@ namespace ICSharpCode.Decompiler.IL @@ -210,17 +251,21 @@ namespace ICSharpCode.Decompiler.IL
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return ArgumentSlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (UnaryInstruction)ShallowClone();
clone.Argument = this.argument.Clone();
return clone;
}
internal sealed override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
this.Argument = this.argument.Inline(flagsBefore, context);
return this;
}
protected override InstructionFlags ComputeFlags()
{
return argument.Flags;
@ -242,19 +287,21 @@ namespace ICSharpCode.Decompiler.IL @@ -242,19 +287,21 @@ namespace ICSharpCode.Decompiler.IL
this.Left = left;
this.Right = right;
}
public static readonly SlotInfo LeftSlot = new SlotInfo("Left", canInlineInto: true);
ILInstruction left;
public ILInstruction Left {
get { return this.left; }
set {
ValidateArgument(value);
ValidateChild(value);
SetChildInstruction(ref this.left, value, 0);
}
}
public static readonly SlotInfo RightSlot = new SlotInfo("Right", canInlineInto: true);
ILInstruction right;
public ILInstruction Right {
get { return this.right; }
set {
ValidateArgument(value);
ValidateChild(value);
SetChildInstruction(ref this.right, value, 1);
}
}
@ -286,6 +333,17 @@ namespace ICSharpCode.Decompiler.IL @@ -286,6 +333,17 @@ namespace ICSharpCode.Decompiler.IL
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return LeftSlot;
case 1:
return RightSlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (BinaryInstruction)ShallowClone();
@ -293,12 +351,6 @@ namespace ICSharpCode.Decompiler.IL @@ -293,12 +351,6 @@ namespace ICSharpCode.Decompiler.IL
clone.Right = this.right.Clone();
return clone;
}
internal sealed override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
this.Right = this.right.Inline(flagsBefore | ((this.left.Flags) & ~(InstructionFlags.MayPeek | InstructionFlags.MayPop)), context);
this.Left = this.left.Inline(flagsBefore, context);
return this;
}
protected override InstructionFlags ComputeFlags()
{
return left.Flags | right.Flags;
@ -334,6 +386,7 @@ namespace ICSharpCode.Decompiler.IL @@ -334,6 +386,7 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>A container of IL blocks.</summary>
public sealed partial class ILFunction : ILInstruction
{
public static readonly SlotInfo BodySlot = new SlotInfo("Body");
ILInstruction body;
public ILInstruction Body {
get { return this.body; }
@ -365,6 +418,15 @@ namespace ICSharpCode.Decompiler.IL @@ -365,6 +418,15 @@ namespace ICSharpCode.Decompiler.IL
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return BodySlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (ILFunction)ShallowClone();
@ -636,14 +698,16 @@ namespace ICSharpCode.Decompiler.IL @@ -636,14 +698,16 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>If statement / conditional expression. <c>if (condition) trueExpr else falseExpr</c></summary>
public sealed partial class IfInstruction : ILInstruction
{
public static readonly SlotInfo ConditionSlot = new SlotInfo("Condition", canInlineInto: true);
ILInstruction condition;
public ILInstruction Condition {
get { return this.condition; }
set {
ValidateArgument(value);
ValidateChild(value);
SetChildInstruction(ref this.condition, value, 0);
}
}
public static readonly SlotInfo TrueInstSlot = new SlotInfo("TrueInst");
ILInstruction trueInst;
public ILInstruction TrueInst {
get { return this.trueInst; }
@ -652,6 +716,7 @@ namespace ICSharpCode.Decompiler.IL @@ -652,6 +716,7 @@ namespace ICSharpCode.Decompiler.IL
SetChildInstruction(ref this.trueInst, value, 1);
}
}
public static readonly SlotInfo FalseInstSlot = new SlotInfo("FalseInst");
ILInstruction falseInst;
public ILInstruction FalseInst {
get { return this.falseInst; }
@ -693,6 +758,19 @@ namespace ICSharpCode.Decompiler.IL @@ -693,6 +758,19 @@ namespace ICSharpCode.Decompiler.IL
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return ConditionSlot;
case 1:
return TrueInstSlot;
case 2:
return FalseInstSlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (IfInstruction)ShallowClone();
@ -728,6 +806,7 @@ namespace ICSharpCode.Decompiler.IL @@ -728,6 +806,7 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>Switch section within a switch statement</summary>
public sealed partial class SwitchSection : ILInstruction
{
public static readonly SlotInfo BodySlot = new SlotInfo("Body");
ILInstruction body;
public ILInstruction Body {
get { return this.body; }
@ -759,6 +838,15 @@ namespace ICSharpCode.Decompiler.IL @@ -759,6 +838,15 @@ namespace ICSharpCode.Decompiler.IL
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return BodySlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (SwitchSection)ShallowClone();
@ -800,14 +888,16 @@ namespace ICSharpCode.Decompiler.IL @@ -800,14 +888,16 @@ namespace ICSharpCode.Decompiler.IL
Debug.Assert(variable != null);
this.variable = variable;
}
public static readonly SlotInfo FilterSlot = new SlotInfo("Filter");
ILInstruction filter;
public ILInstruction Filter {
get { return this.filter; }
set {
ValidateArgument(value);
ValidateChild(value);
SetChildInstruction(ref this.filter, value, 0);
}
}
public static readonly SlotInfo BodySlot = new SlotInfo("Body");
ILInstruction body;
public ILInstruction Body {
get { return this.body; }
@ -844,6 +934,17 @@ namespace ICSharpCode.Decompiler.IL @@ -844,6 +934,17 @@ namespace ICSharpCode.Decompiler.IL
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return FilterSlot;
case 1:
return BodySlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (TryCatchHandler)ShallowClone();
@ -1135,11 +1236,12 @@ namespace ICSharpCode.Decompiler.IL @@ -1135,11 +1236,12 @@ namespace ICSharpCode.Decompiler.IL
readonly ILVariable variable;
/// <summary>Returns the variable operand.</summary>
public ILVariable Variable { get { return variable; } }
public static readonly SlotInfo ValueSlot = new SlotInfo("Value", canInlineInto: true);
ILInstruction value;
public ILInstruction Value {
get { return this.value; }
set {
ValidateArgument(value);
ValidateChild(value);
SetChildInstruction(ref this.value, value, 0);
}
}
@ -1166,17 +1268,21 @@ namespace ICSharpCode.Decompiler.IL @@ -1166,17 +1268,21 @@ namespace ICSharpCode.Decompiler.IL
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return ValueSlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (StLoc)ShallowClone();
clone.Value = this.value.Clone();
return clone;
}
internal sealed override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
this.Value = this.value.Inline(flagsBefore, context);
return this;
}
public override StackType ResultType { get { return variable.StackType; } }
protected override InstructionFlags ComputeFlags()
{
@ -1201,6 +1307,82 @@ namespace ICSharpCode.Decompiler.IL @@ -1201,6 +1307,82 @@ namespace ICSharpCode.Decompiler.IL
}
}
/// <summary>Stores the value into an anonymous temporary variable, and returns the address of that variable.</summary>
public sealed partial class AddressOf : ILInstruction
{
public AddressOf(ILInstruction value) : base(OpCode.AddressOf)
{
this.Value = value;
}
public static readonly SlotInfo ValueSlot = new SlotInfo("Value", canInlineInto: true);
ILInstruction value;
public ILInstruction Value {
get { return this.value; }
set {
ValidateChild(value);
SetChildInstruction(ref this.value, value, 0);
}
}
protected sealed override int GetChildCount()
{
return 1;
}
protected sealed override ILInstruction GetChild(int index)
{
switch (index) {
case 0:
return this.value;
default:
throw new IndexOutOfRangeException();
}
}
protected sealed override void SetChild(int index, ILInstruction value)
{
switch (index) {
case 0:
this.Value = value;
break;
default:
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return ValueSlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (AddressOf)ShallowClone();
clone.Value = this.value.Clone();
return clone;
}
public override StackType ResultType { get { return StackType.Ref; } }
protected override InstructionFlags ComputeFlags()
{
return value.Flags;
}
public override void WriteTo(ITextOutput output)
{
output.Write(OpCode);
output.Write('(');
this.value.WriteTo(output);
output.Write(')');
}
public override void AcceptVisitor(ILVisitor visitor)
{
visitor.VisitAddressOf(this);
}
public override T AcceptVisitor<T>(ILVisitor<T> visitor)
{
return visitor.VisitAddressOf(this);
}
}
/// <summary>Loads a constant string.</summary>
public sealed partial class LdStr : SimpleInstruction
{
@ -1510,11 +1692,12 @@ namespace ICSharpCode.Decompiler.IL @@ -1510,11 +1692,12 @@ namespace ICSharpCode.Decompiler.IL
this.Target = target;
this.field = field;
}
public static readonly SlotInfo TargetSlot = new SlotInfo("Target", canInlineInto: true);
ILInstruction target;
public ILInstruction Target {
get { return this.target; }
set {
ValidateArgument(value);
ValidateChild(value);
SetChildInstruction(ref this.target, value, 0);
}
}
@ -1541,17 +1724,21 @@ namespace ICSharpCode.Decompiler.IL @@ -1541,17 +1724,21 @@ namespace ICSharpCode.Decompiler.IL
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return TargetSlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (LdFld)ShallowClone();
clone.Target = this.target.Clone();
return clone;
}
internal sealed override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
this.Target = this.target.Inline(flagsBefore, context);
return this;
}
/// <summary>Gets/Sets whether the memory access is volatile.</summary>
public bool IsVolatile { get; set; }
/// <summary>Returns the alignment specified by the 'unaligned' prefix; or 0 if there was no 'unaligned' prefix.</summary>
@ -1595,11 +1782,12 @@ namespace ICSharpCode.Decompiler.IL @@ -1595,11 +1782,12 @@ namespace ICSharpCode.Decompiler.IL
this.Target = target;
this.field = field;
}
public static readonly SlotInfo TargetSlot = new SlotInfo("Target", canInlineInto: true);
ILInstruction target;
public ILInstruction Target {
get { return this.target; }
set {
ValidateArgument(value);
ValidateChild(value);
SetChildInstruction(ref this.target, value, 0);
}
}
@ -1626,17 +1814,21 @@ namespace ICSharpCode.Decompiler.IL @@ -1626,17 +1814,21 @@ namespace ICSharpCode.Decompiler.IL
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return TargetSlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (LdFlda)ShallowClone();
clone.Target = this.target.Clone();
return clone;
}
internal sealed override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
this.Target = this.target.Inline(flagsBefore, context);
return this;
}
readonly IField field;
/// <summary>Returns the field operand.</summary>
public IField Field { get { return field; } }
@ -1673,19 +1865,21 @@ namespace ICSharpCode.Decompiler.IL @@ -1673,19 +1865,21 @@ namespace ICSharpCode.Decompiler.IL
this.Value = value;
this.field = field;
}
public static readonly SlotInfo TargetSlot = new SlotInfo("Target", canInlineInto: true);
ILInstruction target;
public ILInstruction Target {
get { return this.target; }
set {
ValidateArgument(value);
ValidateChild(value);
SetChildInstruction(ref this.target, value, 0);
}
}
public static readonly SlotInfo ValueSlot = new SlotInfo("Value", canInlineInto: true);
ILInstruction value;
public ILInstruction Value {
get { return this.value; }
set {
ValidateArgument(value);
ValidateChild(value);
SetChildInstruction(ref this.value, value, 1);
}
}
@ -1717,6 +1911,17 @@ namespace ICSharpCode.Decompiler.IL @@ -1717,6 +1911,17 @@ namespace ICSharpCode.Decompiler.IL
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return TargetSlot;
case 1:
return ValueSlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (StFld)ShallowClone();
@ -1724,12 +1929,6 @@ namespace ICSharpCode.Decompiler.IL @@ -1724,12 +1929,6 @@ namespace ICSharpCode.Decompiler.IL
clone.Value = this.value.Clone();
return clone;
}
internal sealed override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
this.Value = this.value.Inline(flagsBefore | ((this.target.Flags) & ~(InstructionFlags.MayPeek | InstructionFlags.MayPop)), context);
this.Target = this.target.Inline(flagsBefore, context);
return this;
}
/// <summary>Gets/Sets whether the memory access is volatile.</summary>
public bool IsVolatile { get; set; }
/// <summary>Returns the alignment specified by the 'unaligned' prefix; or 0 if there was no 'unaligned' prefix.</summary>
@ -1841,11 +2040,12 @@ namespace ICSharpCode.Decompiler.IL @@ -1841,11 +2040,12 @@ namespace ICSharpCode.Decompiler.IL
this.Value = value;
this.field = field;
}
public static readonly SlotInfo ValueSlot = new SlotInfo("Value", canInlineInto: true);
ILInstruction value;
public ILInstruction Value {
get { return this.value; }
set {
ValidateArgument(value);
ValidateChild(value);
SetChildInstruction(ref this.value, value, 0);
}
}
@ -1872,17 +2072,21 @@ namespace ICSharpCode.Decompiler.IL @@ -1872,17 +2072,21 @@ namespace ICSharpCode.Decompiler.IL
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return ValueSlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (StsFld)ShallowClone();
clone.Value = this.value.Clone();
return clone;
}
internal sealed override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
this.Value = this.value.Inline(flagsBefore, context);
return this;
}
/// <summary>Gets/Sets whether the memory access is volatile.</summary>
public bool IsVolatile { get; set; }
/// <summary>Returns the alignment specified by the 'unaligned' prefix; or 0 if there was no 'unaligned' prefix.</summary>
@ -1990,11 +2194,12 @@ namespace ICSharpCode.Decompiler.IL @@ -1990,11 +2194,12 @@ namespace ICSharpCode.Decompiler.IL
this.Target = target;
this.type = type;
}
public static readonly SlotInfo TargetSlot = new SlotInfo("Target", canInlineInto: true);
ILInstruction target;
public ILInstruction Target {
get { return this.target; }
set {
ValidateArgument(value);
ValidateChild(value);
SetChildInstruction(ref this.target, value, 0);
}
}
@ -2021,17 +2226,21 @@ namespace ICSharpCode.Decompiler.IL @@ -2021,17 +2226,21 @@ namespace ICSharpCode.Decompiler.IL
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return TargetSlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (LdObj)ShallowClone();
clone.Target = this.target.Clone();
return clone;
}
internal sealed override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
this.Target = this.target.Inline(flagsBefore, context);
return this;
}
readonly IType type;
/// <summary>Returns the type operand.</summary>
public IType Type { get { return type; } }
@ -2076,19 +2285,21 @@ namespace ICSharpCode.Decompiler.IL @@ -2076,19 +2285,21 @@ namespace ICSharpCode.Decompiler.IL
this.Value = value;
this.type = type;
}
public static readonly SlotInfo TargetSlot = new SlotInfo("Target", canInlineInto: true);
ILInstruction target;
public ILInstruction Target {
get { return this.target; }
set {
ValidateArgument(value);
ValidateChild(value);
SetChildInstruction(ref this.target, value, 0);
}
}
public static readonly SlotInfo ValueSlot = new SlotInfo("Value", canInlineInto: true);
ILInstruction value;
public ILInstruction Value {
get { return this.value; }
set {
ValidateArgument(value);
ValidateChild(value);
SetChildInstruction(ref this.value, value, 1);
}
}
@ -2120,6 +2331,17 @@ namespace ICSharpCode.Decompiler.IL @@ -2120,6 +2331,17 @@ namespace ICSharpCode.Decompiler.IL
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return TargetSlot;
case 1:
return ValueSlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (StObj)ShallowClone();
@ -2127,12 +2349,6 @@ namespace ICSharpCode.Decompiler.IL @@ -2127,12 +2349,6 @@ namespace ICSharpCode.Decompiler.IL
clone.Value = this.value.Clone();
return clone;
}
internal sealed override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
this.Value = this.value.Inline(flagsBefore | ((this.target.Flags) & ~(InstructionFlags.MayPeek | InstructionFlags.MayPop)), context);
this.Target = this.target.Inline(flagsBefore, context);
return this;
}
readonly IType type;
/// <summary>Returns the type operand.</summary>
public IType Type { get { return type; } }
@ -2300,11 +2516,12 @@ namespace ICSharpCode.Decompiler.IL @@ -2300,11 +2516,12 @@ namespace ICSharpCode.Decompiler.IL
readonly IType type;
/// <summary>Returns the type operand.</summary>
public IType Type { get { return type; } }
public static readonly SlotInfo SizeSlot = new SlotInfo("Size", canInlineInto: true);
ILInstruction size;
public ILInstruction Size {
get { return this.size; }
set {
ValidateArgument(value);
ValidateChild(value);
SetChildInstruction(ref this.size, value, 0);
}
}
@ -2331,17 +2548,21 @@ namespace ICSharpCode.Decompiler.IL @@ -2331,17 +2548,21 @@ namespace ICSharpCode.Decompiler.IL
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return SizeSlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (NewArr)ShallowClone();
clone.Size = this.size.Clone();
return clone;
}
internal sealed override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
this.Size = this.size.Inline(flagsBefore, context);
return this;
}
public override StackType ResultType { get { return StackType.O; } }
protected override InstructionFlags ComputeFlags()
{
@ -2469,11 +2690,12 @@ namespace ICSharpCode.Decompiler.IL @@ -2469,11 +2690,12 @@ namespace ICSharpCode.Decompiler.IL
{
this.Array = array;
}
public static readonly SlotInfo ArraySlot = new SlotInfo("Array", canInlineInto: true);
ILInstruction array;
public ILInstruction Array {
get { return this.array; }
set {
ValidateArgument(value);
ValidateChild(value);
SetChildInstruction(ref this.array, value, 0);
}
}
@ -2500,17 +2722,21 @@ namespace ICSharpCode.Decompiler.IL @@ -2500,17 +2722,21 @@ namespace ICSharpCode.Decompiler.IL
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return ArraySlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (LdLen)ShallowClone();
clone.Array = this.array.Clone();
return clone;
}
internal sealed override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
this.Array = this.array.Inline(flagsBefore, context);
return this;
}
public override StackType ResultType { get { return StackType.I; } }
protected override InstructionFlags ComputeFlags()
{
@ -2542,19 +2768,21 @@ namespace ICSharpCode.Decompiler.IL @@ -2542,19 +2768,21 @@ namespace ICSharpCode.Decompiler.IL
this.Index = index;
this.type = type;
}
public static readonly SlotInfo ArraySlot = new SlotInfo("Array", canInlineInto: true);
ILInstruction array;
public ILInstruction Array {
get { return this.array; }
set {
ValidateArgument(value);
ValidateChild(value);
SetChildInstruction(ref this.array, value, 0);
}
}
public static readonly SlotInfo IndexSlot = new SlotInfo("Index", canInlineInto: true);
ILInstruction index;
public ILInstruction Index {
get { return this.index; }
set {
ValidateArgument(value);
ValidateChild(value);
SetChildInstruction(ref this.index, value, 1);
}
}
@ -2586,6 +2814,17 @@ namespace ICSharpCode.Decompiler.IL @@ -2586,6 +2814,17 @@ namespace ICSharpCode.Decompiler.IL
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return ArraySlot;
case 1:
return IndexSlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (LdElema)ShallowClone();
@ -2593,12 +2832,6 @@ namespace ICSharpCode.Decompiler.IL @@ -2593,12 +2832,6 @@ namespace ICSharpCode.Decompiler.IL
clone.Index = this.index.Clone();
return clone;
}
internal sealed override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
this.Index = this.index.Inline(flagsBefore | ((this.array.Flags) & ~(InstructionFlags.MayPeek | InstructionFlags.MayPop)), context);
this.Array = this.array.Inline(flagsBefore, context);
return this;
}
readonly IType type;
/// <summary>Returns the type operand.</summary>
public IType Type { get { return type; } }
@ -2789,6 +3022,10 @@ namespace ICSharpCode.Decompiler.IL @@ -2789,6 +3022,10 @@ namespace ICSharpCode.Decompiler.IL
{
Default(inst);
}
protected internal virtual void VisitAddressOf(AddressOf inst)
{
Default(inst);
}
protected internal virtual void VisitLdStr(LdStr inst)
{
Default(inst);
@ -3083,6 +3320,10 @@ namespace ICSharpCode.Decompiler.IL @@ -3083,6 +3320,10 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst);
}
protected internal virtual T VisitAddressOf(AddressOf inst)
{
return Default(inst);
}
protected internal virtual T VisitLdStr(LdStr inst)
{
return Default(inst);
@ -3313,6 +3554,7 @@ namespace ICSharpCode.Decompiler.IL @@ -3313,6 +3554,7 @@ namespace ICSharpCode.Decompiler.IL
"ldloc",
"ldloca",
"stloc",
"addressof",
"ldstr",
"ldc.i4",
"ldc.i8",
@ -3608,6 +3850,16 @@ namespace ICSharpCode.Decompiler.IL @@ -3608,6 +3850,16 @@ namespace ICSharpCode.Decompiler.IL
value = default(ILInstruction);
return false;
}
public bool MatchAddressOf(out ILInstruction value)
{
var inst = this as AddressOf;
if (inst != null) {
value = inst.Value;
return true;
}
value = default(ILInstruction);
return false;
}
public bool MatchLdStr(out string value)
{
var inst = this as LdStr;

70
ICSharpCode.Decompiler/IL/Instructions.tt

@ -24,6 +24,8 @@ @@ -24,6 +24,8 @@
<#@ output extension=".cs" #>
<#
OpCode[] baseClasses = {
new OpCode("SimpleInstruction", "Instruction without any arguments",
AbstractBaseClass, CustomArguments(), CustomWriteTo),
new OpCode("UnaryInstruction", "Instruction with a single argument",
AbstractBaseClass, CustomArguments("argument")),
new OpCode("BinaryInstruction", "Instruction with two arguments: Left and Right",
@ -60,7 +62,7 @@ @@ -60,7 +62,7 @@
new OpCode("if", "If statement / conditional expression. <c>if (condition) trueExpr else falseExpr</c>",
CustomClassName("IfInstruction"),
CustomChildren(new []{
new ChildInfo("condition") { IsArgument = true },
new ArgumentInfo("condition"),
new ChildInfo("trueInst"),
new ChildInfo("falseInst"),
}), CustomConstructor, CustomComputeFlags, CustomWriteTo),
@ -73,7 +75,7 @@ @@ -73,7 +75,7 @@
BaseClass("TryInstruction"), CustomConstructor, CustomComputeFlags, CustomWriteTo),
new OpCode("try.catch.handler", "Catch handler within a try-catch statement.",
CustomChildren(new [] {
new ChildInfo("filter") { IsArgument = true },
new ChildInfo("filter"),
new ChildInfo("body"),
}), HasVariableOperand, CustomWriteTo, CustomComputeFlags),
new OpCode("try.finally", "Try-finally statement",
@ -106,6 +108,9 @@ @@ -106,6 +108,9 @@
new OpCode("stloc", "Stores a value into a local variable. (starg/stloc)",
CustomClassName("StLoc"), HasVariableOperand, CustomArguments("value"),
ResultType("variable.StackType")),
new OpCode("addressof", "Stores the value into an anonymous temporary variable, and returns the address of that variable.",
CustomClassName("AddressOf"), CustomArguments("value"), ResultType("Ref")),
new OpCode("ldstr", "Loads a constant string.",
CustomClassName("LdStr"), LoadConstant("string"), ResultType("O")),
new OpCode("ldc.i4", "Loads a constant 32-bit integer.",
@ -557,19 +562,40 @@ namespace ICSharpCode.Decompiler.IL @@ -557,19 +562,40 @@ namespace ICSharpCode.Decompiler.IL
static Action<OpCode> CustomArguments(params string[] arguments)
{
return CustomChildren(arguments.Select(arg => new ChildInfo(arg) { IsArgument = true}).ToArray(), generateInline: true);
return CustomChildren(arguments.Select(arg => new ArgumentInfo(arg)).ToArray(), generateInline: true);
}
class ChildInfo
{
public readonly string PropertyName;
public readonly string Name;
public bool IsArgument;
public readonly string SlotName;
public bool CanInlineInto;
public ChildInfo(string name)
{
this.Name = name;
this.PropertyName = MakeName(name);
this.SlotName = this.PropertyName + "Slot";
}
public string GetSlotInit()
{
StringBuilder b = new StringBuilder();
b.Append("new SlotInfo(\"" + PropertyName + "\"");
if (CanInlineInto)
b.Append(", canInlineInto: true");
b.Append(")");
return b.ToString();
}
}
class ArgumentInfo : ChildInfo
{
public ArgumentInfo(string name) : base(name)
{
this.CanInlineInto = true;
}
}
@ -589,11 +615,12 @@ namespace ICSharpCode.Decompiler.IL @@ -589,11 +615,12 @@ namespace ICSharpCode.Decompiler.IL
if (i > 0)
opCode.WriteArguments.Add("output.Write(\", \");");
opCode.WriteArguments.Add("this." + arg + ".WriteTo(output);");
opCode.Members.Add("public static readonly SlotInfo " + children[i].SlotName + " = " + children[i].GetSlotInit() + ";");
opCode.Members.Add("ILInstruction " + arg + ";");
opCode.Members.Add("public ILInstruction " + argProp + " {" + Environment.NewLine
+ "\tget { return this." + arg + "; }" + Environment.NewLine
+ "\tset {" + Environment.NewLine
+ "\t\t" + (children[i].IsArgument ? "ValidateArgument" : "ValidateChild") + "(value);" + Environment.NewLine
+ "\t\tValidateChild(value);" + Environment.NewLine
+ "\t\tSetChildInstruction(ref this." + arg + ", value, " + i + ");" + Environment.NewLine
+ "\t}" + Environment.NewLine
+ "}");
@ -636,6 +663,20 @@ namespace ICSharpCode.Decompiler.IL @@ -636,6 +663,20 @@ namespace ICSharpCode.Decompiler.IL
b.Append("}");
opCode.Members.Add(b.ToString());
b = new StringBuilder();
b.AppendLine("protected sealed override SlotInfo GetChildSlot(int index)");
b.AppendLine("{");
b.AppendLine("\tswitch (index) {");
for (int i = 0; i < children.Length; i++) {
b.AppendLine("\t\tcase " + i + ":");
b.AppendLine("\t\t\treturn " + children[i].SlotName + ";");
}
b.AppendLine("\t\tdefault:");
b.AppendLine("\t\t\tthrow new IndexOutOfRangeException();");
b.AppendLine("\t}");
b.Append("}");
opCode.Members.Add(b.ToString());
b = new StringBuilder();
b.AppendLine("public sealed override ILInstruction Clone()");
b.AppendLine("{");
@ -646,25 +687,6 @@ namespace ICSharpCode.Decompiler.IL @@ -646,25 +687,6 @@ namespace ICSharpCode.Decompiler.IL
b.AppendLine("\treturn clone;");
b.Append("}");
opCode.Members.Add(b.ToString());
if (generateInline) {
b = new StringBuilder();
b.AppendLine("internal sealed override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)");
b.AppendLine("{");
for (int i = children.Length - 1; i >= 0; i--) {
string arg = children[i].Name;
b.Append("\tthis." + MakeName(arg) + " = this." + arg + ".Inline(flagsBefore");
if (i > 0) {
b.Append(" | ((");
b.Append(string.Join(" | ", children.Take(i).Select(child2 => "this." + child2.Name + ".Flags")));
b.Append(") & ~(InstructionFlags.MayPeek | InstructionFlags.MayPop))");
}
b.AppendLine(", context);");
}
b.AppendLine("\treturn this;");
b.Append("}");
opCode.Members.Add(b.ToString());
}
};
}

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

@ -30,35 +30,22 @@ namespace ICSharpCode.Decompiler.IL @@ -30,35 +30,22 @@ namespace ICSharpCode.Decompiler.IL
{
/// <summary>
/// A block consists of a list of IL instructions.
/// <para>
/// Phase-1 execution of a block is a no-op: any peek/pop instructions within the block are ignored at this stage.
/// </para>
/// <para>
/// Phase-2 execution will execute the instructions in order, pseudo-code:
/// </para>
/// <code>
/// foreach (var inst in Instructions) {
/// var result = inst.Phase1().Phase2();
/// if (result != void) evalStack.Push(result);
/// }
/// return FinalInstruction.Phase1().Phase2();
/// </code>
///
/// <para>
/// Note: if execution reaches the end of the instruction list,
/// the FinalInstruction (which is not part of the list) will be executed.
/// The block returns returns the result value of the FinalInstruction.
/// For blocks returning void, the FinalInstruction will usually be 'nop'.
///
/// TODO: now that 'implicit push' is gone, reconsider whether we need
/// a separate slot for FinalInstruction
/// </para>
/// </summary>
/// <remarks>
/// Fun fact: wrapping a pop instruction in a block
/// (<c>new Block { FinalInstruction = popInst }</c>) turns it
/// from a phase-1 pop instruction to a phase-2 pop instruction.
/// However, this is just of theoretical interest; we currently don't plan to use inline blocks that
/// pop elements that they didn't push themselves.
/// </remarks>
partial class Block : ILInstruction
{
public static readonly SlotInfo InstructionSlot = new SlotInfo("Instruction", isCollection: true);
public static readonly SlotInfo FinalInstructionSlot = new SlotInfo("FinalInstruction");
public readonly InstructionCollection<ILInstruction> Instructions;
ILInstruction finalInstruction;
@ -168,15 +155,19 @@ namespace ICSharpCode.Decompiler.IL @@ -168,15 +155,19 @@ namespace ICSharpCode.Decompiler.IL
Instructions[index] = value;
}
protected override SlotInfo GetChildSlot(int index)
{
if (index == Instructions.Count)
return InstructionSlot;
else
return FinalInstructionSlot;
}
protected override InstructionFlags ComputeFlags()
{
var flags = InstructionFlags.None;
foreach (var inst in Instructions) {
flags |= Phase1Boundary(inst.Flags);
if (inst.ResultType != StackType.Void) {
// implicit push
flags |= InstructionFlags.MayWriteEvaluationStack;
}
}
flags |= Phase1Boundary(FinalInstruction.Flags);
return flags;
@ -187,16 +178,10 @@ namespace ICSharpCode.Decompiler.IL @@ -187,16 +178,10 @@ namespace ICSharpCode.Decompiler.IL
/// The MayPop and MayPeek flags are removed and converted into
/// MayReadEvaluationStack and/or MayWriteEvaluationStack flags.
/// </summary>
[Obsolete("there's not phase-1 evaluation anymore")]
[Obsolete("there's no phase-1 evaluation anymore")]
internal static InstructionFlags Phase1Boundary(InstructionFlags flags)
{
return flags;
}
internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
// an inline block has no phase-1 effects, so we're immediately done with inlining
return this;
}
}
}

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

@ -38,6 +38,7 @@ namespace ICSharpCode.Decompiler.IL @@ -38,6 +38,7 @@ namespace ICSharpCode.Decompiler.IL
/// </summary>
partial class BlockContainer : ILInstruction
{
public readonly SlotInfo BlockSlot = new SlotInfo("Block", isCollection: true);
public readonly InstructionCollection<Block> Blocks;
/// <summary>
@ -133,6 +134,11 @@ namespace ICSharpCode.Decompiler.IL @@ -133,6 +134,11 @@ namespace ICSharpCode.Decompiler.IL
throw new InvalidOperationException("Cannot replace blocks in BlockContainer");
}
protected override SlotInfo GetChildSlot(int index)
{
return BlockSlot;
}
internal override void CheckInvariant()
{
base.CheckInvariant();
@ -154,12 +160,6 @@ namespace ICSharpCode.Decompiler.IL @@ -154,12 +160,6 @@ namespace ICSharpCode.Decompiler.IL
flags &= ~InstructionFlags.EndPointUnreachable;
return flags;
}
internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
// Blocks are phase-1 boundaries
return this;
}
}
}

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

@ -29,6 +29,8 @@ namespace ICSharpCode.Decompiler.IL @@ -29,6 +29,8 @@ namespace ICSharpCode.Decompiler.IL
{
public abstract class CallInstruction : ILInstruction
{
public static readonly SlotInfo ArgumentSlot = new SlotInfo("Argument", canInlineInto: true, isCollection: true);
public static CallInstruction Create(OpCode opCode, IMethod method)
{
switch (opCode) {
@ -98,6 +100,11 @@ namespace ICSharpCode.Decompiler.IL @@ -98,6 +100,11 @@ namespace ICSharpCode.Decompiler.IL
Arguments[index] = value;
}
protected override SlotInfo GetChildSlot(int index)
{
return ArgumentSlot;
}
protected override InstructionFlags ComputeFlags()
{
var flags = InstructionFlags.MayThrow | InstructionFlags.SideEffect;
@ -125,18 +132,5 @@ namespace ICSharpCode.Decompiler.IL @@ -125,18 +132,5 @@ namespace ICSharpCode.Decompiler.IL
}
output.Write(')');
}
internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
InstructionFlags operandFlags = InstructionFlags.None;
for (int i = 0; i < Arguments.Count - 1; i++) {
operandFlags |= Arguments[i].Flags;
}
flagsBefore |= operandFlags & ~(InstructionFlags.MayPeek | InstructionFlags.MayPop);
for (int i = Arguments.Count - 1; i >= 0; i--) {
Arguments[i] = Arguments[i].Inline(flagsBefore, context);
}
return this;
}
}
}

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

@ -62,11 +62,5 @@ namespace ICSharpCode.Decompiler.IL @@ -62,11 +62,5 @@ namespace ICSharpCode.Decompiler.IL
// We intentionally don't propagate any flags from the lambda body!
return InstructionFlags.MayThrow;
}
internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
// To the outside, lambda creation looks like a constant
return this;
}
}
}

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

@ -39,15 +39,6 @@ namespace ICSharpCode.Decompiler.IL @@ -39,15 +39,6 @@ namespace ICSharpCode.Decompiler.IL
this.OpCode = opCode;
}
protected void ValidateArgument(ILInstruction inst)
{
if (inst == null)
throw new ArgumentNullException("inst");
if (inst.ResultType == StackType.Void)
throw new ArgumentException("Argument must not be of type void", "inst");
Debug.Assert(!this.IsDescendantOf(inst), "ILAst must form a tree");
}
protected void ValidateChild(ILInstruction inst)
{
if (inst == null)
@ -128,6 +119,12 @@ namespace ICSharpCode.Decompiler.IL @@ -128,6 +119,12 @@ namespace ICSharpCode.Decompiler.IL
/// </summary>
public Interval ILRange;
public void AddILRange(Interval ilRange)
{
// TODO: try to combine the two ranges
this.ILRange = ilRange;
}
/// <summary>
/// Writes the ILAst to the text output.
/// </summary>
@ -162,6 +159,7 @@ namespace ICSharpCode.Decompiler.IL @@ -162,6 +159,7 @@ namespace ICSharpCode.Decompiler.IL
protected abstract int GetChildCount();
protected abstract ILInstruction GetChild(int index);
protected abstract void SetChild(int index, ILInstruction value);
protected abstract SlotInfo GetChildSlot(int index);
#region ChildrenCollection + ChildrenEnumerator
public struct ChildrenCollection : IReadOnlyList<ILInstruction>
@ -290,6 +288,7 @@ namespace ICSharpCode.Decompiler.IL @@ -290,6 +288,7 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>
/// Returns all descendants of the ILInstruction in post-order.
/// (including the ILInstruction itself)
/// </summary>
/// <remarks>
/// Within a loop 'foreach (var node in inst.Descendants)', it is illegal to
@ -324,26 +323,10 @@ namespace ICSharpCode.Decompiler.IL @@ -324,26 +323,10 @@ namespace ICSharpCode.Decompiler.IL
stack.Pop().Dispose();
}
}
yield return this;
}
}
/// <summary>
/// Attempts inlining from the inline context 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="context">The inline context providing the values on the evaluation stack.</param>
/// <returns>
/// 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>
/// Number of parents that refer to this instruction and are connected to the root.
/// Usually is 0 for unconnected nodes and 1 for connected nodes, but may temporarily increase to 2
@ -405,6 +388,16 @@ namespace ICSharpCode.Decompiler.IL @@ -405,6 +388,16 @@ namespace ICSharpCode.Decompiler.IL
/// </summary>
public int ChildIndex { get; internal set; }
/// <summary>
/// Gets information about the slot in which this instruction is stored.
/// (i.e., the relation of this instruction to its parent instruction)
/// </summary>
public SlotInfo SlotInfo {
get {
return parent.GetChildSlot(this.ChildIndex);
}
}
/// <summary>
/// Replaces a child of this ILInstruction.
/// </summary>

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

@ -52,13 +52,6 @@ namespace ICSharpCode.Decompiler.IL @@ -52,13 +52,6 @@ namespace ICSharpCode.Decompiler.IL
}
}
internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
this.Condition = condition.Inline(flagsBefore, context);
// note: we skip TrueInst and FalseInst because there's a phase-1-boundary around them
return this;
}
protected override InstructionFlags ComputeFlags()
{
return condition.Flags | Block.Phase1Boundary(CombineFlags(trueInst.Flags, falseInst.Flags));

20
ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs

@ -33,32 +33,36 @@ namespace ICSharpCode.Decompiler.IL @@ -33,32 +33,36 @@ namespace ICSharpCode.Decompiler.IL
return inst != null && inst.Variable == variable;
}
public bool MatchLdLoca(ILVariable variable)
{
var inst = this as LdLoca;
return inst != null && inst.Variable == variable;
}
public bool MatchLdThis()
{
var inst = this as LdLoc;
return inst != null && inst.Variable.Kind == VariableKind.This;
}
public bool MatchStLoc(ILVariable variable, out ILInstruction value)
public bool MatchStLoc(out ILVariable variable)
{
var inst = this as StLoc;
if (inst != null && inst.Variable == variable) {
value = inst.Value;
if (inst != null) {
variable = inst.Variable;
return true;
}
value = null;
variable = null;
return false;
}
public bool MatchStLoc(out ILVariable variable, out ILInstruction value)
public bool MatchStLoc(ILVariable variable, out ILInstruction value)
{
var inst = this as StLoc;
if (inst != null) {
variable = inst.Variable;
if (inst != null && inst.Variable == variable) {
value = inst.Value;
return true;
}
variable = null;
value = null;
return false;
}

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

@ -24,6 +24,8 @@ namespace ICSharpCode.Decompiler.IL @@ -24,6 +24,8 @@ namespace ICSharpCode.Decompiler.IL
{
partial class Return
{
public static readonly SlotInfo ReturnValueSlot = new SlotInfo("ReturnValue", canInlineInto: true);
ILInstruction returnValue;
/// <summary>
@ -33,7 +35,7 @@ namespace ICSharpCode.Decompiler.IL @@ -33,7 +35,7 @@ namespace ICSharpCode.Decompiler.IL
get { return returnValue; }
set {
if (value != null)
ValidateArgument(value);
ValidateChild(value);
SetChildInstruction(ref returnValue, value, 0);
}
}
@ -95,11 +97,9 @@ namespace ICSharpCode.Decompiler.IL @@ -95,11 +97,9 @@ namespace ICSharpCode.Decompiler.IL
throw new IndexOutOfRangeException();
}
internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
protected override SlotInfo GetChildSlot(int index)
{
if (returnValue != null)
this.ReturnValue = returnValue.Inline(flagsBefore, context);
return this;
return ReturnValueSlot;
}
}
}

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

@ -28,46 +28,16 @@ namespace ICSharpCode.Decompiler.IL @@ -28,46 +28,16 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>
/// A simple instruction that does not have any arguments.
/// </summary>
public abstract class SimpleInstruction : ILInstruction
public abstract partial class SimpleInstruction : ILInstruction
{
protected SimpleInstruction(OpCode opCode) : base(opCode)
{
}
public override void WriteTo(ITextOutput output)
{
output.Write(OpCode);
}
protected sealed override int GetChildCount()
{
return 0;
}
protected sealed override ILInstruction GetChild(int index)
{
throw new IndexOutOfRangeException();
}
protected sealed override void SetChild(int index, ILInstruction value)
{
throw new IndexOutOfRangeException();
}
public sealed override ILInstruction Clone()
{
return ShallowClone();
}
protected override InstructionFlags ComputeFlags()
{
return InstructionFlags.None;
}
internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
// Nothing to do, since we don't have arguments.
return this;
}
}
}

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

@ -26,6 +26,9 @@ namespace ICSharpCode.Decompiler.IL @@ -26,6 +26,9 @@ namespace ICSharpCode.Decompiler.IL
/// </summary>
partial class SwitchInstruction
{
public static readonly SlotInfo ValueSlot = new SlotInfo("Value", canInlineInto: true);
public static readonly SlotInfo SectionSlot = new SlotInfo("Section", isCollection: true);
public SwitchInstruction(ILInstruction value)
: base(OpCode.SwitchInstruction)
{
@ -44,12 +47,6 @@ namespace ICSharpCode.Decompiler.IL @@ -44,12 +47,6 @@ namespace ICSharpCode.Decompiler.IL
public readonly InstructionCollection<SwitchSection> Sections;
internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
Value = value.Inline(flagsBefore, context);
return this;
}
protected override InstructionFlags ComputeFlags()
{
var sectionFlags = InstructionFlags.None;
@ -93,6 +90,13 @@ namespace ICSharpCode.Decompiler.IL @@ -93,6 +90,13 @@ namespace ICSharpCode.Decompiler.IL
Sections[index - 1] = (SwitchSection)value;
}
protected override SlotInfo GetChildSlot(int index)
{
if (index == 0)
return ValueSlot;
return SectionSlot;
}
public override ILInstruction Clone()
{
var clone = new SwitchInstruction(value.Clone());
@ -113,14 +117,6 @@ namespace ICSharpCode.Decompiler.IL @@ -113,14 +117,6 @@ namespace ICSharpCode.Decompiler.IL
public LongSet Labels { get; set; }
internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
// Inlining into switch sections would be madness.
// To keep phase-1 execution semantics consistent with inlining, there's a
// phase-1-boundary around every switch section.
return this;
}
protected override InstructionFlags ComputeFlags()
{
return Block.Phase1Boundary(body.Flags);

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

@ -25,6 +25,8 @@ namespace ICSharpCode.Decompiler.IL @@ -25,6 +25,8 @@ namespace ICSharpCode.Decompiler.IL
{
public abstract class TryInstruction : ILInstruction
{
public static readonly SlotInfo TryBlockSlot = new SlotInfo("TryBlock");
protected TryInstruction(OpCode opCode, ILInstruction tryBlock) : base(opCode)
{
this.TryBlock = tryBlock;
@ -38,14 +40,6 @@ namespace ICSharpCode.Decompiler.IL @@ -38,14 +40,6 @@ namespace ICSharpCode.Decompiler.IL
SetChildInstruction(ref this.tryBlock, value, 0);
}
}
internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
// Inlining into exception-handling constructs would be madness.
// To keep phase-1 execution semantics consistent with inlining, there's a
// phase-1-boundary around every try/catch/finally/fault block.
return this;
}
}
/// <summary>
@ -58,6 +52,7 @@ namespace ICSharpCode.Decompiler.IL @@ -58,6 +52,7 @@ namespace ICSharpCode.Decompiler.IL
/// </remarks>
partial class TryCatch : TryInstruction
{
public static readonly SlotInfo HandlerSlot = new SlotInfo("Handler", isCollection: true);
public readonly InstructionCollection<TryCatchHandler> Handlers;
public TryCatch(ILInstruction tryBlock) : base(OpCode.TryCatch, tryBlock)
@ -115,6 +110,14 @@ namespace ICSharpCode.Decompiler.IL @@ -115,6 +110,14 @@ namespace ICSharpCode.Decompiler.IL
else
Handlers[index - 1] = (TryCatchHandler)value;
}
protected override SlotInfo GetChildSlot(int index)
{
if (index == 0)
return TryBlockSlot;
else
return HandlerSlot;
}
}
/// <summary>
@ -131,12 +134,6 @@ namespace ICSharpCode.Decompiler.IL @@ -131,12 +134,6 @@ namespace ICSharpCode.Decompiler.IL
/// </summary>
partial class TryCatchHandler
{
internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context)
{
// should never happen as TryCatchHandler only appears within TryCatch instructions
throw new InvalidOperationException();
}
internal override void CheckInvariant()
{
base.CheckInvariant();
@ -183,6 +180,8 @@ namespace ICSharpCode.Decompiler.IL @@ -183,6 +180,8 @@ namespace ICSharpCode.Decompiler.IL
partial class TryFinally
{
public static readonly SlotInfo FinallyBlockSlot = new SlotInfo("FinallyBlock");
public TryFinally(ILInstruction tryBlock, ILInstruction finallyBlock) : base(OpCode.TryFinally, tryBlock)
{
this.FinallyBlock = finallyBlock;
@ -254,10 +253,24 @@ namespace ICSharpCode.Decompiler.IL @@ -254,10 +253,24 @@ namespace ICSharpCode.Decompiler.IL
throw new IndexOutOfRangeException();
}
}
protected override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return TryBlockSlot;
case 1:
return FinallyBlockSlot;
default:
throw new IndexOutOfRangeException();
}
}
}
partial class TryFault
{
public static readonly SlotInfo FaultBlockSlot = new SlotInfo("FaultBlock");
public TryFault(ILInstruction tryBlock, ILInstruction faultBlock) : base(OpCode.TryFinally, tryBlock)
{
this.FaultBlock = faultBlock;
@ -328,5 +341,17 @@ namespace ICSharpCode.Decompiler.IL @@ -328,5 +341,17 @@ namespace ICSharpCode.Decompiler.IL
throw new IndexOutOfRangeException();
}
}
protected override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return TryBlockSlot;
case 1:
return FaultBlockSlot;
default:
throw new IndexOutOfRangeException();
}
}
}
}

24
ICSharpCode.Decompiler/IL/SemanticHelper.cs

@ -24,23 +24,27 @@ namespace ICSharpCode.Decompiler.IL @@ -24,23 +24,27 @@ namespace ICSharpCode.Decompiler.IL
{
// TODO: consider moving IfInstruction.CombineFlags and Block.Phase1Boundary here
/// <summary>
/// Gets whether instruction is pure:
/// * must not have side effects
/// * must not throw exceptions
/// * must not branch
/// </summary>
internal static bool IsPure(InstructionFlags inst)
{
const InstructionFlags pureFlags = InstructionFlags.MayReadLocals;
return (inst & ~pureFlags) == 0;
}
/// <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 an impure action, we cannot reorder them
const InstructionFlags pureFlags =
InstructionFlags.MayPeek
| InstructionFlags.MayReadEvaluationStack
| InstructionFlags.MayReadLocals;
if ((inst1 & inst2 & ~pureFlags) != 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.MayReadEvaluationStack, InstructionFlags.MayWriteEvaluationStack))
if (!IsPure(inst1) && !IsPure(inst2))
return false;
// We cannot reorder if inst2 might write what inst1 looks at
if (ConflictingPair(inst1, inst2, InstructionFlags.MayReadLocals, InstructionFlags.MayWriteLocals | InstructionFlags.SideEffect))
return false;
return true;

44
ICSharpCode.Decompiler/IL/SlotInfo.cs

@ -0,0 +1,44 @@ @@ -0,0 +1,44 @@
/*
* Created by SharpDevelop.
* User: Daniel
* Date: 2015-05-31
* Time: 14:26
*
* To change this template use Tools | Options | Coding | Edit Standard Headers.
*/
using System;
namespace ICSharpCode.Decompiler.IL
{
/// <summary>
/// Holds information about the role of an instruction within its parent instruction.
/// </summary>
public class SlotInfo
{
public static SlotInfo None = new SlotInfo("<no slot>");
/// <summary>
/// Gets the name of the slot.
/// </summary>
public readonly string Name;
/// <summary>
/// Gets whether it is possible to inline into this slot.
/// </summary>
public readonly bool CanInlineInto;
public readonly bool IsCollection;
public SlotInfo(string name, bool canInlineInto = false, bool isCollection = false)
{
this.IsCollection = isCollection;
this.Name = name;
this.CanInlineInto = canInlineInto;
}
public override string ToString()
{
return Name;
}
}
}

318
ICSharpCode.Decompiler/ILAst/ILInlining.cs → ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
// Copyright (c) 2011-2015 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
@ -21,121 +21,31 @@ using System.Collections.Generic; @@ -21,121 +21,31 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Mono.Cecil;
using ICSharpCode.Decompiler.IL;
namespace ICSharpCode.Decompiler.ILAst
namespace ICSharpCode.Decompiler.IL
{
/// <summary>
/// Performs inlining transformations.
/// </summary>
public class ILInlining
public class ILInlining : IILTransform
{
readonly ILBlock method;
internal Dictionary<ILVariable, int> numStloc = new Dictionary<ILVariable, int>();
internal Dictionary<ILVariable, int> numLdloc = new Dictionary<ILVariable, int>();
internal Dictionary<ILVariable, int> numLdloca = new Dictionary<ILVariable, int>();
public ILInlining(ILBlock method)
{
this.method = method;
AnalyzeMethod();
}
void AnalyzeMethod()
{
numStloc.Clear();
numLdloc.Clear();
numLdloca.Clear();
// Analyse the whole method
AnalyzeNode(method);
}
/// <summary>
/// For each variable reference, adds <paramref name="direction"/> to the num* dicts.
/// Direction will be 1 for analysis, and -1 when removing a node from analysis.
/// </summary>
void AnalyzeNode(ILNode node, int direction = 1)
public void Run(ILFunction function, ILTransformContext context)
{
ILExpression expr = node as ILExpression;
if (expr != null) {
ILVariable locVar = expr.Operand as ILVariable;
if (locVar != null) {
if (expr.Code == ILCode.Stloc) {
numStloc[locVar] = numStloc.GetOrDefault(locVar) + direction;
} else if (expr.Code == ILCode.Ldloc) {
numLdloc[locVar] = numLdloc.GetOrDefault(locVar) + direction;
} else if (expr.Code == ILCode.Ldloca) {
numLdloca[locVar] = numLdloca.GetOrDefault(locVar) + direction;
} else {
throw new NotSupportedException(expr.Code.ToString());
}
}
foreach (ILExpression child in expr.Arguments)
AnalyzeNode(child, direction);
} else {
var catchBlock = node as ILTryCatchBlock.CatchBlock;
if (catchBlock != null && catchBlock.ExceptionVariable != null) {
numStloc[catchBlock.ExceptionVariable] = numStloc.GetOrDefault(catchBlock.ExceptionVariable) + direction;
}
foreach (ILNode child in node.GetChildren())
AnalyzeNode(child, direction);
foreach (var block in function.Descendants.OfType<Block>()) {
InlineAllInBlock(block);
}
}
public bool InlineAllVariables()
{
bool modified = false;
ILInlining i = new ILInlining(method);
foreach (ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>())
modified |= i.InlineAllInBlock(block);
return modified;
}
public bool InlineAllInBlock(ILBlock block)
{
bool modified = false;
List<ILNode> body = block.Body;
if (block is ILTryCatchBlock.CatchBlock && body.Count > 1) {
ILVariable v = ((ILTryCatchBlock.CatchBlock)block).ExceptionVariable;
if (v != null && v.IsGenerated) {
if (numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 1 && numLdloc.GetOrDefault(v) == 1) {
ILVariable v2;
ILExpression ldException;
if (body[0].Match(ILCode.Stloc, out v2, out ldException) && ldException.MatchLdloc(v)) {
body.RemoveAt(0);
((ILTryCatchBlock.CatchBlock)block).ExceptionVariable = v2;
modified = true;
}
}
}
}
for(int i = 0; i < body.Count - 1;) {
ILVariable locVar;
ILExpression expr;
if (body[i].Match(ILCode.Stloc, out locVar, out expr) && InlineOneIfPossible(block.Body, i, aggressive: false)) {
modified = true;
i = Math.Max(0, i - 1); // Go back one step
} else {
i++;
}
}
foreach(ILBasicBlock bb in body.OfType<ILBasicBlock>()) {
modified |= InlineAllInBasicBlock(bb);
}
return modified;
}
public bool InlineAllInBasicBlock(ILBasicBlock bb)
public bool InlineAllInBlock(Block block)
{
bool modified = false;
List<ILNode> body = bb.Body;
for(int i = 0; i < body.Count;) {
ILVariable locVar;
ILExpression expr;
if (body[i].Match(ILCode.Stloc, out locVar, out expr) && InlineOneIfPossible(bb.Body, i, aggressive: false)) {
int i = 0;
while (i < block.Instructions.Count) {
if (InlineOneIfPossible(block, i, aggressive: false)) {
modified = true;
i = Math.Max(0, i - 1); // Go back one step
i = Math.Max(0, i - 1);
// Go back one step
} else {
i++;
}
@ -144,19 +54,16 @@ namespace ICSharpCode.Decompiler.ILAst @@ -144,19 +54,16 @@ namespace ICSharpCode.Decompiler.ILAst
}
/// <summary>
/// Inlines instructions before pos into block.Body[pos].
/// Inlines instructions before pos into block.Instructions[pos].
/// </summary>
/// <returns>The number of instructions that were inlined.</returns>
public int InlineInto(List<ILNode> body, int pos, bool aggressive)
public int InlineInto(Block block, int pos, bool aggressive)
{
if (pos >= body.Count)
if (pos >= block.Instructions.Count)
return 0;
int count = 0;
while (--pos >= 0) {
ILExpression expr = body[pos] as ILExpression;
if (expr == null || expr.Code != ILCode.Stloc)
break;
if (InlineOneIfPossible(body, pos, aggressive))
if (InlineOneIfPossible(block, pos, aggressive))
count++;
else
break;
@ -171,10 +78,10 @@ namespace ICSharpCode.Decompiler.ILAst @@ -171,10 +78,10 @@ namespace ICSharpCode.Decompiler.ILAst
/// <remarks>
/// After the operation, pos will point to the new combined instruction.
/// </remarks>
public bool InlineIfPossible(List<ILNode> body, ref int pos)
public bool InlineIfPossible(Block block, ref int pos)
{
if (InlineOneIfPossible(body, pos, true)) {
pos -= InlineInto(body, pos, false);
if (InlineOneIfPossible(block, pos, true)) {
pos -= InlineInto(block, pos, false);
return true;
}
return false;
@ -183,29 +90,30 @@ namespace ICSharpCode.Decompiler.ILAst @@ -183,29 +90,30 @@ namespace ICSharpCode.Decompiler.ILAst
/// <summary>
/// Inlines the stloc instruction at block.Body[pos] into the next instruction, if possible.
/// </summary>
public bool InlineOneIfPossible(List<ILNode> body, int pos, bool aggressive)
public bool InlineOneIfPossible(Block block, int pos, bool aggressive)
{
ILVariable v;
ILExpression inlinedExpression;
if (body[pos].Match(ILCode.Stloc, out v, out inlinedExpression) && !v.IsPinned) {
if (InlineIfPossible(v, inlinedExpression, body.ElementAtOrDefault(pos+1), aggressive)) {
StLoc stloc = block.Instructions[pos] as StLoc;
if (stloc != null && stloc.Variable.Kind != VariableKind.PinnedLocal) {
ILVariable v = stloc.Variable;
if (InlineIfPossible(v, stloc.Value, block.Instructions.ElementAtOrDefault(pos+1), aggressive)) {
// Assign the ranges of the stloc instruction:
inlinedExpression.ILRanges.AddRange(((ILExpression)body[pos]).ILRanges);
stloc.Value.AddILRange(stloc.ILRange);
// Remove the stloc instruction:
body.RemoveAt(pos);
Debug.Assert(block.Instructions[pos] == stloc);
block.Instructions.RemoveAt(pos);
return true;
} else if (numLdloc.GetOrDefault(v) == 0 && numLdloca.GetOrDefault(v) == 0) {
} else if (v.LoadCount == 0 && v.AddressCount == 0) {
// The variable is never loaded
if (inlinedExpression.HasNoSideEffects()) {
// Remove completely
AnalyzeNode(body[pos], -1);
body.RemoveAt(pos);
if (SemanticHelper.IsPure(stloc.Value.Flags)) {
// Remove completely if the instruction has no effects
// (except for reading locals)
block.Instructions.RemoveAt(pos);
return true;
} else if (inlinedExpression.CanBeExpressionStatement() && v.IsGenerated) {
} else if (v.Kind == VariableKind.StackSlot) {
// Assign the ranges of the stloc instruction:
inlinedExpression.ILRanges.AddRange(((ILExpression)body[pos]).ILRanges);
stloc.Value.AddILRange(stloc.ILRange);
// Remove the stloc, but keep the inner expression
body[pos] = inlinedExpression;
stloc.ReplaceWith(stloc.Value);
return true;
}
}
@ -216,46 +124,40 @@ namespace ICSharpCode.Decompiler.ILAst @@ -216,46 +124,40 @@ namespace ICSharpCode.Decompiler.ILAst
/// <summary>
/// Inlines 'expr' into 'next', if possible.
/// </summary>
bool InlineIfPossible(ILVariable v, ILExpression inlinedExpression, ILNode next, bool aggressive)
bool InlineIfPossible(ILVariable v, ILInstruction inlinedExpression, ILInstruction next, bool aggressive)
{
// ensure the variable is accessed only a single time
if (numStloc.GetOrDefault(v) != 1)
if (v.StoreCount != 1)
return false;
int ldloc = numLdloc.GetOrDefault(v);
if (ldloc > 1 || ldloc + numLdloca.GetOrDefault(v) != 1)
if (v.LoadCount > 1 || v.LoadCount + v.AddressCount != 1)
return false;
if (next is ILCondition)
next = ((ILCondition)next).Condition;
else if (next is ILWhileLoop)
next = ((ILWhileLoop)next).Condition;
ILExpression parent;
int pos;
if (FindLoadInNext(next as ILExpression, v, inlinedExpression, out parent, out pos) == true) {
if (ldloc == 0) {
if (!IsGeneratedValueTypeTemporary((ILExpression)next, parent, pos, v, inlinedExpression))
return false;
ILInstruction loadInst;
if (FindLoadInNext(next, v, inlinedExpression, out loadInst) == true) {
if (loadInst.OpCode == OpCode.LdLoca) {
//if (!IsGeneratedValueTypeTemporary((ILInstruction)next, loadInst, v, inlinedExpression))
return false;
} else {
if (!aggressive && !v.IsGenerated && !NonAggressiveInlineInto((ILExpression)next, parent, inlinedExpression))
Debug.Assert(loadInst.OpCode == OpCode.LdLoc);
if (!aggressive && v.Kind != VariableKind.StackSlot && !NonAggressiveInlineInto(next, loadInst, inlinedExpression))
return false;
}
// Assign the ranges of the ldloc instruction:
inlinedExpression.ILRanges.AddRange(parent.Arguments[pos].ILRanges);
inlinedExpression.AddILRange(loadInst.ILRange);
if (ldloc == 0) {
// it was an ldloca instruction, so we need to use the pseudo-opcode 'addressof' so that the types
// comes out correctly
parent.Arguments[pos] = new ILExpression(ILCode.AddressOf, null, inlinedExpression);
if (loadInst.OpCode == OpCode.LdLoca) {
// it was an ldloca instruction, so we need to use the pseudo-opcode 'addressof'
// to preserve the semantics of the compiler-generated temporary
loadInst.ReplaceWith(new AddressOf(inlinedExpression));
} else {
parent.Arguments[pos] = inlinedExpression;
loadInst.ReplaceWith(inlinedExpression);
}
return true;
}
return false;
}
/*
/// <summary>
/// Is this a temporary variable generated by the C# compiler for instance method calls on value type values
/// </summary>
@ -264,7 +166,7 @@ namespace ICSharpCode.Decompiler.ILAst @@ -264,7 +166,7 @@ namespace ICSharpCode.Decompiler.ILAst
/// <param name="pos">Index of the load within 'parent'</param>
/// <param name="v">The variable being inlined.</param>
/// <param name="inlinedExpression">The expression being inlined</param>
bool IsGeneratedValueTypeTemporary(ILExpression next, ILExpression parent, int pos, ILVariable v, ILExpression inlinedExpression)
bool IsGeneratedValueTypeTemporary(ILInstruction next, ILInstruction parent, int pos, ILVariable v, ILInstruction inlinedExpression)
{
if (pos == 0 && v.Type != null && v.Type.IsValueType) {
// Inlining a value type variable is allowed only if the resulting code will maintain the semantics
@ -317,7 +219,7 @@ namespace ICSharpCode.Decompiler.ILAst @@ -317,7 +219,7 @@ namespace ICSharpCode.Decompiler.ILAst
case ILCode.Castclass:
case ILCode.Unbox_Any:
// These are valid, but might occur as part of a foreach loop variable.
ILExpression arg = inlinedExpression.Arguments[0];
ILInstruction arg = inlinedExpression.Arguments[0];
if (arg.Code == ILCode.CallGetter || arg.Code == ILCode.CallvirtGetter || arg.Code == ILCode.Call || arg.Code == ILCode.Callvirt) {
mr = (MethodReference)arg.Operand;
if (mr.Name == "get_Current" && mr.HasThis)
@ -345,24 +247,28 @@ namespace ICSharpCode.Decompiler.ILAst @@ -345,24 +247,28 @@ namespace ICSharpCode.Decompiler.ILAst
}
return false;
}
*/
/// <summary>
/// Determines whether a variable should be inlined in non-aggressive mode, even though it is not a generated variable.
/// </summary>
/// <param name="next">The next top-level expression</param>
/// <param name="parent">The direct parent of the load within 'next'</param>
/// <param name="loadInst">The load within 'next'</param>
/// <param name="inlinedExpression">The expression being inlined</param>
bool NonAggressiveInlineInto(ILExpression next, ILExpression parent, ILExpression inlinedExpression)
bool NonAggressiveInlineInto(ILInstruction next, ILInstruction loadInst, ILInstruction inlinedExpression)
{
if (inlinedExpression.Code == ILCode.DefaultValue)
Debug.Assert(loadInst.IsDescendantOf(next));
if (inlinedExpression.OpCode == OpCode.DefaultValue)
return true;
switch (next.Code) {
case ILCode.Ret:
case ILCode.Brtrue:
var parent = loadInst.Parent;
switch (next.OpCode) {
case OpCode.Return:
case OpCode.IfInstruction:
return parent == next;
case ILCode.Switch:
return parent == next || (parent.Code == ILCode.Sub && parent == next.Arguments[0]);
case OpCode.SwitchInstruction:
return parent == next || (parent.OpCode == OpCode.Sub && parent.Parent == next);
default:
return false;
}
@ -371,37 +277,32 @@ namespace ICSharpCode.Decompiler.ILAst @@ -371,37 +277,32 @@ namespace ICSharpCode.Decompiler.ILAst
/// <summary>
/// Gets whether 'expressionBeingMoved' can be inlined into 'expr'.
/// </summary>
public bool CanInlineInto(ILExpression expr, ILVariable v, ILExpression expressionBeingMoved)
public bool CanInlineInto(ILInstruction expr, ILVariable v, ILInstruction expressionBeingMoved)
{
ILExpression parent;
int pos;
return FindLoadInNext(expr, v, expressionBeingMoved, out parent, out pos) == true;
ILInstruction loadInst;
return FindLoadInNext(expr, v, expressionBeingMoved, out loadInst) == true;
}
/// <summary>
/// Finds the position to inline to.
/// </summary>
/// <returns>true = found; false = cannot continue search; null = not found</returns>
bool? FindLoadInNext(ILExpression expr, ILVariable v, ILExpression expressionBeingMoved, out ILExpression parent, out int pos)
bool? FindLoadInNext(ILInstruction expr, ILVariable v, ILInstruction expressionBeingMoved, out ILInstruction loadInst)
{
parent = null;
pos = 0;
loadInst = null;
if (expr == null)
return false;
for (int i = 0; i < expr.Arguments.Count; i++) {
// Stop when seeing an opcode that does not guarantee that its operands will be evaluated.
// Inlining in that case might result in the inlined expresion not being evaluted.
if (i == 1 && (expr.Code == ILCode.LogicAnd || expr.Code == ILCode.LogicOr || expr.Code == ILCode.TernaryOp || expr.Code == ILCode.NullCoalescing))
if (expr.MatchLdLoc(v) || expr.MatchLdLoca(v)) {
// Match found, we can inline
loadInst = expr;
return true;
}
foreach (var child in expr.Children) {
if (!child.SlotInfo.CanInlineInto)
return false;
ILExpression arg = expr.Arguments[i];
if ((arg.Code == ILCode.Ldloc || arg.Code == ILCode.Ldloca) && arg.Operand == v) {
parent = expr;
pos = i;
return true;
}
bool? r = FindLoadInNext(arg, v, expressionBeingMoved, out parent, out pos);
// Recursively try to find the load instruction
bool? r = FindLoadInNext(child, v, expressionBeingMoved, out loadInst);
if (r != null)
return r;
}
@ -414,40 +315,25 @@ namespace ICSharpCode.Decompiler.ILAst @@ -414,40 +315,25 @@ namespace ICSharpCode.Decompiler.ILAst
/// <summary>
/// Determines whether it is safe to move 'expressionBeingMoved' past 'expr'
/// </summary>
bool IsSafeForInlineOver(ILExpression expr, ILExpression expressionBeingMoved)
bool IsSafeForInlineOver(ILInstruction expr, ILInstruction expressionBeingMoved)
{
switch (expr.Code) {
case ILCode.Ldloc:
ILVariable loadedVar = (ILVariable)expr.Operand;
if (numLdloca.GetOrDefault(loadedVar) != 0) {
// abort, inlining is not possible
return false;
}
foreach (ILExpression potentialStore in expressionBeingMoved.GetSelfAndChildrenRecursive<ILExpression>()) {
if (potentialStore.Code == ILCode.Stloc && potentialStore.Operand == loadedVar)
return false;
}
// the expression is loading a non-forbidden variable
return true;
case ILCode.Ldloca:
case ILCode.Ldflda:
case ILCode.Ldsflda:
case ILCode.Ldelema:
case ILCode.AddressOf:
case ILCode.ValueOf:
case ILCode.NullableOf:
// address-loading instructions are safe if their arguments are safe
foreach (ILExpression arg in expr.Arguments) {
if (!IsSafeForInlineOver(arg, expressionBeingMoved))
ILVariable v;
if (expr.MatchLdLoc(out v) && v.AddressCount == 0) {
// MayReorder() only looks at flags, so it doesn't
// allow reordering 'stloc x y; ldloc v' to 'ldloc v; stloc x y'
// We'll allow the reordering unless x==v
if (expressionBeingMoved.HasFlag(InstructionFlags.MayWriteLocals)) {
foreach (var stloc in expressionBeingMoved.Descendants.OfType<StLoc>()) {
if (stloc.Variable == v)
return false;
}
return true;
default:
// instructions with no side-effects are safe (except for Ldloc and Ldloca which are handled separately)
return expr.HasNoSideEffects();
}
return true;
}
return SemanticHelper.MayReorder(expressionBeingMoved.Flags, expr.Flags);
}
/*
/// <summary>
/// Runs a very simple form of copy propagation.
/// Copy propagation is used in two cases:
@ -461,7 +347,7 @@ namespace ICSharpCode.Decompiler.ILAst @@ -461,7 +347,7 @@ namespace ICSharpCode.Decompiler.ILAst
foreach (ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) {
for (int i = 0; i < block.Body.Count; i++) {
ILVariable v;
ILExpression copiedExpr;
ILInstruction copiedExpr;
if (block.Body[i].Match(ILCode.Stloc, out v, out copiedExpr)
&& !v.IsParameter && numStloc.GetOrDefault(v) == 1 && numLdloca.GetOrDefault(v) == 0
&& CanPerformCopyPropagation(copiedExpr, v))
@ -470,16 +356,16 @@ namespace ICSharpCode.Decompiler.ILAst @@ -470,16 +356,16 @@ namespace ICSharpCode.Decompiler.ILAst
ILVariable[] uninlinedArgs = new ILVariable[copiedExpr.Arguments.Count];
for (int j = 0; j < uninlinedArgs.Length; j++) {
uninlinedArgs[j] = new ILVariable { IsGenerated = true, Name = v.Name + "_cp_" + j };
block.Body.Insert(i++, new ILExpression(ILCode.Stloc, uninlinedArgs[j], copiedExpr.Arguments[j]));
block.Body.Insert(i++, new ILInstruction(ILCode.Stloc, uninlinedArgs[j], copiedExpr.Arguments[j]));
}
// perform copy propagation:
foreach (var expr in method.GetSelfAndChildrenRecursive<ILExpression>()) {
foreach (var expr in method.GetSelfAndChildrenRecursive<ILInstruction>()) {
if (expr.Code == ILCode.Ldloc && expr.Operand == v) {
expr.Code = copiedExpr.Code;
expr.Operand = copiedExpr.Operand;
for (int j = 0; j < uninlinedArgs.Length; j++) {
expr.Arguments.Add(new ILExpression(ILCode.Ldloc, uninlinedArgs[j]));
expr.Arguments.Add(new ILInstruction(ILCode.Ldloc, uninlinedArgs[j]));
}
}
}
@ -496,7 +382,7 @@ namespace ICSharpCode.Decompiler.ILAst @@ -496,7 +382,7 @@ namespace ICSharpCode.Decompiler.ILAst
}
}
bool CanPerformCopyPropagation(ILExpression expr, ILVariable copyVariable)
bool CanPerformCopyPropagation(ILInstruction expr, ILVariable copyVariable)
{
switch (expr.Code) {
case ILCode.Ldloca:
@ -519,6 +405,6 @@ namespace ICSharpCode.Decompiler.ILAst @@ -519,6 +405,6 @@ namespace ICSharpCode.Decompiler.ILAst
default:
return false;
}
}
}*/
}
}

4
ICSharpCode.Decompiler/IL/Transforms/OptimizingTransform.cs

@ -49,7 +49,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -49,7 +49,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (ret.ReturnValue != null && ret.ReturnValue.MatchLdLoc(out v)
&& v.IsSingleUse && block.Instructions[0].MatchStLoc(v, out inst))
{
ret.ReturnValue = block.Instructions[0];
inst.AddILRange(ret.ReturnValue.ILRange);
inst.AddILRange(block.Instructions[0].ILRange);
ret.ReturnValue = inst;
block.Instructions.RemoveAt(0);
}
}

5
ICSharpCode.Decompiler/IL/Transforms/TransformingVisitor.cs

@ -225,10 +225,9 @@ namespace ICSharpCode.Decompiler.IL @@ -225,10 +225,9 @@ namespace ICSharpCode.Decompiler.IL
// If the block has only one instruction, we can remove the block too
// (but only if this doesn't change the pop-order in the phase 1 evaluation of the parent block)
if (block.Instructions.Count == 0) {
if (!block.FinalInstruction.HasFlag(InstructionFlags.MayPeek | InstructionFlags.MayPop))
return block.FinalInstruction;
return block.FinalInstruction;
} else if (block.Instructions.Count == 1 && block.FinalInstruction.OpCode == OpCode.Nop) {
if (block.Instructions[0].ResultType == StackType.Void && !block.Instructions[0].HasFlag(InstructionFlags.MayPeek | InstructionFlags.MayPop))
if (block.Instructions[0].ResultType == StackType.Void)
return block.Instructions[0];
}
return block;

1
ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj

@ -108,6 +108,7 @@ @@ -108,6 +108,7 @@
<Compile Include="TestCases\HelloWorld.cs" />
<Compile Include="TestCases\PropertiesAndEvents.cs" />
<Compile Include="TestCases\Switch.cs" />
<Compile Include="TestCases\ValueTypeCall.cs" />
<Compile Include="TestRunner.cs" />
<Compile Include="Util\LongSetTests.cs" />
<Compile Include="CustomAttributes\CustomAttributeTests.cs" />

62
ICSharpCode.Decompiler/Tests/TestCases/ValueTypeCall.cs

@ -0,0 +1,62 @@ @@ -0,0 +1,62 @@
using System;
namespace ValueTypeCall
{
public struct MutValueType
{
public int val;
public void Increment()
{
Console.WriteLine("Inc() called on {0}", val);
val++;
}
}
public class Program
{
public static void Main()
{
MutValueType m = new MutValueType();
RefParameter(ref m);
ValueParameter(m);
Field();
Box();
}
static void RefParameter(ref MutValueType m)
{
m.Increment();
m.Increment();
}
static void ValueParameter(MutValueType m)
{
m.Increment();
m.Increment();
}
static readonly MutValueType ReadonlyField = new MutValueType { val = 100 };
static MutValueType MutableField = new MutValueType { val = 200 };
static void Field()
{
ReadonlyField.Increment();
ReadonlyField.Increment();
MutableField.Increment();
MutableField.Increment();
// Ensure that 'v' isn't incorrectly removed
// as a compiler-generated temporary
MutValueType v = MutableField;
v.Increment();
Console.WriteLine("Final value in MutableField: " + MutableField.val);
}
static void Box()
{
object o = new MutValueType { val = 300 };
((MutValueType)o).Increment();
((MutValueType)o).Increment();
}
}
}

6
ICSharpCode.Decompiler/Tests/TestRunner.cs

@ -64,6 +64,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -64,6 +64,12 @@ namespace ICSharpCode.Decompiler.Tests
TestCompileDecompileCompileOutputAll("Generics.cs");
}
[Test]
public void ValueTypeCall()
{
TestCompileDecompileCompileOutputAll("ValueTypeCall.cs");
}
void TestCompileDecompileCompileOutputAll(string testFileName)
{
TestCompileDecompileCompileOutput(testFileName, CompilerOptions.None);

Loading…
Cancel
Save