Browse Source

Merge pull request #1129 from icsharpcode/userdef-compound-assignment

Userdef compound assignment
pull/1143/head
Daniel Grunwald 7 years ago committed by GitHub
parent
commit
f7a314b64c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4007
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs
  2. 22360
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.il
  3. 20605
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.opt.il
  4. 23858
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.opt.roslyn.il
  5. 26275
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.roslyn.il
  6. 71
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  7. 6
      ICSharpCode.Decompiler/CSharp/Transforms/PrettifyAssignments.cs
  8. 8
      ICSharpCode.Decompiler/IL/ILTypeExtensions.cs
  9. 219
      ICSharpCode.Decompiler/IL/Instructions.cs
  10. 21
      ICSharpCode.Decompiler/IL/Instructions.tt
  11. 94
      ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs
  12. 4
      ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs
  13. 8
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  14. 3
      ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs
  15. 3
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  16. 23
      ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs
  17. 245
      ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs

4007
ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs

File diff suppressed because it is too large Load Diff

22360
ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.il

File diff suppressed because it is too large Load Diff

20605
ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.opt.il

File diff suppressed because it is too large Load Diff

23858
ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.opt.roslyn.il

File diff suppressed because it is too large Load Diff

26275
ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.roslyn.il

File diff suppressed because it is too large Load Diff

71
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -1102,8 +1102,69 @@ namespace ICSharpCode.Decompiler.CSharp
.WithILInstruction(inst) .WithILInstruction(inst)
.WithRR(resolver.ResolveBinaryOperator(op, left.ResolveResult, right.ResolveResult)); .WithRR(resolver.ResolveBinaryOperator(op, left.ResolveResult, right.ResolveResult));
} }
protected internal override TranslatedExpression VisitCompoundAssignmentInstruction(CompoundAssignmentInstruction inst, TranslationContext context) protected internal override TranslatedExpression VisitUserDefinedCompoundAssign(UserDefinedCompoundAssign inst, TranslationContext context)
{
var target = Translate(inst.Target);
if (inst.Method.Parameters.Count == 2) {
var value = Translate(inst.Value).ConvertTo(inst.Method.Parameters[1].Type, this);
AssignmentOperatorType? op = GetAssignmentOperatorTypeFromMetadataName(inst.Method.Name);
Debug.Assert(op != null);
return new AssignmentExpression(target, op.Value, value)
.WithILInstruction(inst)
.WithRR(new OperatorResolveResult(inst.Method.ReturnType, AssignmentExpression.GetLinqNodeType(op.Value, false), inst.Method, inst.IsLifted, new[] { target.ResolveResult, value.ResolveResult }));
} else {
UnaryOperatorType? op = GetUnaryOperatorTypeFromMetadataName(inst.Method.Name, inst.CompoundAssignmentType == CompoundAssignmentType.EvaluatesToOldValue);
Debug.Assert(op != null);
return new UnaryOperatorExpression(op.Value, target)
.WithILInstruction(inst)
.WithRR(new OperatorResolveResult(inst.Method.ReturnType, UnaryOperatorExpression.GetLinqNodeType(op.Value, false), inst.Method, inst.IsLifted, new[] { target.ResolveResult }));
}
}
internal static AssignmentOperatorType? GetAssignmentOperatorTypeFromMetadataName(string name)
{
switch (name) {
case "op_Addition":
return AssignmentOperatorType.Add;
case "op_Subtraction":
return AssignmentOperatorType.Subtract;
case "op_Multiply":
return AssignmentOperatorType.Multiply;
case "op_Division":
return AssignmentOperatorType.Divide;
case "op_Modulus":
return AssignmentOperatorType.Modulus;
case "op_BitwiseAnd":
return AssignmentOperatorType.BitwiseAnd;
case "op_BitwiseOr":
return AssignmentOperatorType.BitwiseOr;
case "op_ExclusiveOr":
return AssignmentOperatorType.ExclusiveOr;
case "op_LeftShift":
return AssignmentOperatorType.ShiftLeft;
case "op_RightShift":
return AssignmentOperatorType.ShiftRight;
default:
return null;
}
}
internal static UnaryOperatorType? GetUnaryOperatorTypeFromMetadataName(string name, bool isPostfix)
{
switch (name) {
case "op_Increment":
return isPostfix ? UnaryOperatorType.PostIncrement : UnaryOperatorType.Increment;
case "op_Decrement":
return isPostfix ? UnaryOperatorType.PostDecrement : UnaryOperatorType.Decrement;
default:
return null;
}
}
protected internal override TranslatedExpression VisitNumericCompoundAssign(NumericCompoundAssign inst, TranslationContext context)
{ {
switch (inst.Operator) { switch (inst.Operator) {
case BinaryNumericOperator.Add: case BinaryNumericOperator.Add:
@ -1131,7 +1192,7 @@ namespace ICSharpCode.Decompiler.CSharp
} }
} }
TranslatedExpression HandleCompoundAssignment(CompoundAssignmentInstruction inst, AssignmentOperatorType op) TranslatedExpression HandleCompoundAssignment(NumericCompoundAssign inst, AssignmentOperatorType op)
{ {
var target = Translate(inst.Target); var target = Translate(inst.Target);
var value = Translate(inst.Value); var value = Translate(inst.Value);
@ -1196,7 +1257,7 @@ namespace ICSharpCode.Decompiler.CSharp
return resultExpr; return resultExpr;
} }
TranslatedExpression HandleCompoundShift(CompoundAssignmentInstruction inst, AssignmentOperatorType op) TranslatedExpression HandleCompoundShift(NumericCompoundAssign inst, AssignmentOperatorType op)
{ {
Debug.Assert(inst.CompoundAssignmentType == CompoundAssignmentType.EvaluatesToNewValue); Debug.Assert(inst.CompoundAssignmentType == CompoundAssignmentType.EvaluatesToNewValue);
var target = Translate(inst.Target); var target = Translate(inst.Target);
@ -1714,7 +1775,7 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
TranslatedExpression arrayExpr = Translate(inst.Array); TranslatedExpression arrayExpr = Translate(inst.Array);
var arrayType = arrayExpr.Type as ArrayType; var arrayType = arrayExpr.Type as ArrayType;
if (arrayType == null) { if (arrayType == null || !TypeUtils.IsCompatibleTypeForMemoryAccess(new ByReferenceType(arrayType.ElementType), inst.Type)) {
arrayType = new ArrayType(compilation, inst.Type, inst.Indices.Count); arrayType = new ArrayType(compilation, inst.Type, inst.Indices.Count);
arrayExpr = arrayExpr.ConvertTo(arrayType, this); arrayExpr = arrayExpr.ConvertTo(arrayType, this);
} }

6
ICSharpCode.Decompiler/CSharp/Transforms/PrettifyAssignments.cs

@ -16,9 +16,12 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.CSharp.Transforms namespace ICSharpCode.Decompiler.CSharp.Transforms
{ {
@ -52,7 +55,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
// TODO: context.Settings.IntroduceIncrementAndDecrement // TODO: context.Settings.IntroduceIncrementAndDecrement
if (assignment.Operator == AssignmentOperatorType.Add || assignment.Operator == AssignmentOperatorType.Subtract) { if (assignment.Operator == AssignmentOperatorType.Add || assignment.Operator == AssignmentOperatorType.Subtract) {
// detect increment/decrement // detect increment/decrement
if (assignment.Right.IsMatch(new PrimitiveExpression(1))) { var rr = assignment.Right.GetResolveResult();
if (rr.IsCompileTimeConstant && rr.Type.IsCSharpPrimitiveIntegerType() && CSharpPrimitiveCast.Cast(rr.Type.GetTypeCode(), 1, false).Equals(rr.ConstantValue)) {
// only if it's not a custom operator // only if it's not a custom operator
if (assignment.Annotation<IL.CallInstruction>() == null) { if (assignment.Annotation<IL.CallInstruction>() == null) {
UnaryOperatorType type; UnaryOperatorType type;

8
ICSharpCode.Decompiler/IL/ILTypeExtensions.cs

@ -163,6 +163,14 @@ namespace ICSharpCode.Decompiler.IL
return new TypeSystem.ByReferenceType(ldflda.Field.Type); return new TypeSystem.ByReferenceType(ldflda.Field.Type);
case LdsFlda ldsflda: case LdsFlda ldsflda:
return new TypeSystem.ByReferenceType(ldsflda.Field.Type); return new TypeSystem.ByReferenceType(ldsflda.Field.Type);
case LdElema ldelema:
if (ldelema.Array.InferType() is TypeSystem.ArrayType arrayType) {
var refType = new TypeSystem.ByReferenceType(arrayType.ElementType);
if (TypeUtils.IsCompatibleTypeForMemoryAccess(refType, ldelema.Type)) {
return refType;
}
}
return new TypeSystem.ByReferenceType(ldelema.Type);
default: default:
return SpecialType.UnknownType; return SpecialType.UnknownType;
} }

219
ICSharpCode.Decompiler/IL/Instructions.cs

@ -45,8 +45,10 @@ namespace ICSharpCode.Decompiler.IL
PinnedRegion, PinnedRegion,
/// <summary>Common instruction for add, sub, mul, div, rem, bit.and, bit.or, bit.xor, shl and shr.</summary> /// <summary>Common instruction for add, sub, mul, div, rem, bit.and, bit.or, bit.xor, shl and shr.</summary>
BinaryNumericInstruction, BinaryNumericInstruction,
/// <summary>Common instruction for compound assignments.</summary> /// <summary>Common instruction for numeric compound assignments.</summary>
CompoundAssignmentInstruction, NumericCompoundAssign,
/// <summary>Common instruction for user-defined compound assignments.</summary>
UserDefinedCompoundAssign,
/// <summary>Bitwise NOT</summary> /// <summary>Bitwise NOT</summary>
BitNot, BitNot,
/// <summary>Retrieves the RuntimeArgumentHandle.</summary> /// <summary>Retrieves the RuntimeArgumentHandle.</summary>
@ -485,6 +487,96 @@ namespace ICSharpCode.Decompiler.IL.Patterns
} }
} }
namespace ICSharpCode.Decompiler.IL namespace ICSharpCode.Decompiler.IL
{
/// <summary>Common instruction for compound assignments.</summary>
public abstract partial class CompoundAssignmentInstruction : ILInstruction
{
public static readonly SlotInfo TargetSlot = new SlotInfo("Target", canInlineInto: true);
ILInstruction target;
public ILInstruction Target {
get { return this.target; }
set {
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 {
ValidateChild(value);
SetChildInstruction(ref this.value, value, 1);
}
}
protected sealed override int GetChildCount()
{
return 2;
}
protected sealed override ILInstruction GetChild(int index)
{
switch (index) {
case 0:
return this.target;
case 1:
return this.value;
default:
throw new IndexOutOfRangeException();
}
}
protected sealed override void SetChild(int index, ILInstruction value)
{
switch (index) {
case 0:
this.Target = value;
break;
case 1:
this.Value = value;
break;
default:
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 = (CompoundAssignmentInstruction)ShallowClone();
clone.Target = this.target.Clone();
clone.Value = this.value.Clone();
return clone;
}
protected override InstructionFlags ComputeFlags()
{
return target.Flags | value.Flags;
}
public override InstructionFlags DirectFlags {
get {
return InstructionFlags.None;
}
}
public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{
ILRange.WriteTo(output, options);
output.Write(OpCode);
output.Write('(');
this.target.WriteTo(output, options);
output.Write(", ");
this.value.WriteTo(output, options);
output.Write(')');
}
}
}
namespace ICSharpCode.Decompiler.IL
{ {
/// <summary>Represents invalid IL. Semantically, this instruction is considered to throw some kind of exception.</summary> /// <summary>Represents invalid IL. Semantically, this instruction is considered to throw some kind of exception.</summary>
public sealed partial class InvalidBranch : SimpleInstruction public sealed partial class InvalidBranch : SimpleInstruction
@ -891,96 +983,66 @@ namespace ICSharpCode.Decompiler.IL
} }
namespace ICSharpCode.Decompiler.IL namespace ICSharpCode.Decompiler.IL
{ {
/// <summary>Common instruction for compound assignments.</summary> /// <summary>Common instruction for numeric compound assignments.</summary>
public sealed partial class CompoundAssignmentInstruction : ILInstruction public sealed partial class NumericCompoundAssign : CompoundAssignmentInstruction
{ {
public static readonly SlotInfo TargetSlot = new SlotInfo("Target", canInlineInto: true); IType type;
ILInstruction target; /// <summary>Returns the type operand.</summary>
public ILInstruction Target { public IType Type {
get { return this.target; } get { return type; }
set { set { type = value; InvalidateFlags(); }
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 {
ValidateChild(value);
SetChildInstruction(ref this.value, value, 1);
}
} }
protected sealed override int GetChildCount() public override StackType ResultType { get { return type.GetStackType(); } }
public override void AcceptVisitor(ILVisitor visitor)
{ {
return 2; visitor.VisitNumericCompoundAssign(this);
} }
protected sealed override ILInstruction GetChild(int index) public override T AcceptVisitor<T>(ILVisitor<T> visitor)
{ {
switch (index) { return visitor.VisitNumericCompoundAssign(this);
case 0:
return this.target;
case 1:
return this.value;
default:
throw new IndexOutOfRangeException();
}
} }
protected sealed override void SetChild(int index, ILInstruction value) public override T AcceptVisitor<C, T>(ILVisitor<C, T> visitor, C context)
{ {
switch (index) { return visitor.VisitNumericCompoundAssign(this, context);
case 0:
this.Target = value;
break;
case 1:
this.Value = value;
break;
default:
throw new IndexOutOfRangeException();
}
} }
protected sealed override SlotInfo GetChildSlot(int index) protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{ {
switch (index) { var o = other as NumericCompoundAssign;
case 0: return o != null && type.Equals(o.type) && CheckForOverflow == o.CheckForOverflow && Sign == o.Sign && Operator == o.Operator && Target.PerformMatch(o.Target, ref match) && Value.PerformMatch(o.Value, ref match);
return TargetSlot;
case 1:
return ValueSlot;
default:
throw new IndexOutOfRangeException();
}
} }
public sealed override ILInstruction Clone() }
}
namespace ICSharpCode.Decompiler.IL
{
/// <summary>Common instruction for user-defined compound assignments.</summary>
public sealed partial class UserDefinedCompoundAssign : CompoundAssignmentInstruction
{
protected override InstructionFlags ComputeFlags()
{ {
var clone = (CompoundAssignmentInstruction)ShallowClone(); return base.ComputeFlags() | InstructionFlags.MayThrow | InstructionFlags.SideEffect;
clone.Target = this.target.Clone();
clone.Value = this.value.Clone();
return clone;
} }
IType type; public override InstructionFlags DirectFlags {
/// <summary>Returns the type operand.</summary> get {
public IType Type { return base.DirectFlags | InstructionFlags.MayThrow | InstructionFlags.SideEffect;
get { return type; } }
set { type = value; InvalidateFlags(); }
} }
public override StackType ResultType { get { return type.GetStackType(); } }
public override void AcceptVisitor(ILVisitor visitor) public override void AcceptVisitor(ILVisitor visitor)
{ {
visitor.VisitCompoundAssignmentInstruction(this); visitor.VisitUserDefinedCompoundAssign(this);
} }
public override T AcceptVisitor<T>(ILVisitor<T> visitor) public override T AcceptVisitor<T>(ILVisitor<T> visitor)
{ {
return visitor.VisitCompoundAssignmentInstruction(this); return visitor.VisitUserDefinedCompoundAssign(this);
} }
public override T AcceptVisitor<C, T>(ILVisitor<C, T> visitor, C context) public override T AcceptVisitor<C, T>(ILVisitor<C, T> visitor, C context)
{ {
return visitor.VisitCompoundAssignmentInstruction(this, context); return visitor.VisitUserDefinedCompoundAssign(this, context);
} }
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match) protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{ {
var o = other as CompoundAssignmentInstruction; var o = other as UserDefinedCompoundAssign;
return o != null && this.target.PerformMatch(o.target, ref match) && this.value.PerformMatch(o.value, ref match) && type.Equals(o.type) && CheckForOverflow == o.CheckForOverflow && Sign == o.Sign && Operator == o.Operator; return o != null && this.Method.Equals(o.Method) && this.CompoundAssignmentType == o.CompoundAssignmentType && Target.PerformMatch(o.Target, ref match) && Value.PerformMatch(o.Value, ref match);
} }
} }
} }
@ -5099,7 +5161,11 @@ namespace ICSharpCode.Decompiler.IL
{ {
Default(inst); Default(inst);
} }
protected internal virtual void VisitCompoundAssignmentInstruction(CompoundAssignmentInstruction inst) protected internal virtual void VisitNumericCompoundAssign(NumericCompoundAssign inst)
{
Default(inst);
}
protected internal virtual void VisitUserDefinedCompoundAssign(UserDefinedCompoundAssign inst)
{ {
Default(inst); Default(inst);
} }
@ -5417,7 +5483,11 @@ namespace ICSharpCode.Decompiler.IL
{ {
return Default(inst); return Default(inst);
} }
protected internal virtual T VisitCompoundAssignmentInstruction(CompoundAssignmentInstruction inst) protected internal virtual T VisitNumericCompoundAssign(NumericCompoundAssign inst)
{
return Default(inst);
}
protected internal virtual T VisitUserDefinedCompoundAssign(UserDefinedCompoundAssign inst)
{ {
return Default(inst); return Default(inst);
} }
@ -5735,7 +5805,11 @@ namespace ICSharpCode.Decompiler.IL
{ {
return Default(inst, context); return Default(inst, context);
} }
protected internal virtual T VisitCompoundAssignmentInstruction(CompoundAssignmentInstruction inst, C context) protected internal virtual T VisitNumericCompoundAssign(NumericCompoundAssign inst, C context)
{
return Default(inst, context);
}
protected internal virtual T VisitUserDefinedCompoundAssign(UserDefinedCompoundAssign inst, C context)
{ {
return Default(inst, context); return Default(inst, context);
} }
@ -6024,7 +6098,8 @@ namespace ICSharpCode.Decompiler.IL
"Block", "Block",
"PinnedRegion", "PinnedRegion",
"binary", "binary",
"compound", "numeric.compound",
"user.compound",
"bit.not", "bit.not",
"arglist", "arglist",
"br", "br",

21
ICSharpCode.Decompiler/IL/Instructions.tt

@ -33,7 +33,9 @@
new OpCode("CallInstruction", "Instruction with a list of arguments.", new OpCode("CallInstruction", "Instruction with a list of arguments.",
AbstractBaseClass, CustomChildren(new []{ new ArgumentInfo("arguments") { IsCollection = true }}), AbstractBaseClass, CustomChildren(new []{ new ArgumentInfo("arguments") { IsCollection = true }}),
CustomWriteTo, MayThrow, SideEffect), CustomWriteTo, MayThrow, SideEffect),
new OpCode("PatternInstruction", "Base class for pattern matching in ILAst.", AbstractBaseClass, ResultType("Unknown")) { Namespace = "ICSharpCode.Decompiler.IL.Patterns" } new OpCode("PatternInstruction", "Base class for pattern matching in ILAst.", AbstractBaseClass, ResultType("Unknown")) { Namespace = "ICSharpCode.Decompiler.IL.Patterns" },
new OpCode("CompoundAssignmentInstruction", "Common instruction for compound assignments.",
AbstractBaseClass, CustomConstructor, CustomArguments(("target", null), ("value", null))),
}; };
OpCode[] opCodes = { OpCode[] opCodes = {
@ -66,10 +68,19 @@
new OpCode("binary", "Common instruction for add, sub, mul, div, rem, bit.and, bit.or, bit.xor, shl and shr.", new OpCode("binary", "Common instruction for add, sub, mul, div, rem, bit.and, bit.or, bit.xor, shl and shr.",
CustomClassName("BinaryNumericInstruction"), Binary, CustomWriteTo, CustomConstructor, CustomComputeFlags, CustomClassName("BinaryNumericInstruction"), Binary, CustomWriteTo, CustomConstructor, CustomComputeFlags,
MatchCondition("CheckForOverflow == o.CheckForOverflow && Sign == o.Sign && Operator == o.Operator && IsLifted == o.IsLifted")), MatchCondition("CheckForOverflow == o.CheckForOverflow && Sign == o.Sign && Operator == o.Operator && IsLifted == o.IsLifted")),
new OpCode("compound", "Common instruction for compound assignments.", new OpCode("numeric.compound", "Common instruction for numeric compound assignments.",
CustomClassName("CompoundAssignmentInstruction"), CustomConstructor, CustomComputeFlags, CustomClassName("NumericCompoundAssign"), BaseClass("CompoundAssignmentInstruction"), CustomConstructor, CustomComputeFlags,
MayThrow, CustomArguments(("target", null), ("value", null)), HasTypeOperand, ResultType("type.GetStackType()"), CustomWriteTo, MayThrow, HasTypeOperand, ResultType("type.GetStackType()"), CustomWriteTo,
MatchCondition("CheckForOverflow == o.CheckForOverflow && Sign == o.Sign && Operator == o.Operator")), MatchCondition("CheckForOverflow == o.CheckForOverflow && Sign == o.Sign && Operator == o.Operator"),
MatchCondition("Target.PerformMatch(o.Target, ref match)"),
MatchCondition("Value.PerformMatch(o.Value, ref match)")),
new OpCode("user.compound", "Common instruction for user-defined compound assignments.",
CustomClassName("UserDefinedCompoundAssign"), BaseClass("CompoundAssignmentInstruction"), CustomConstructor,
MayThrow, SideEffect, CustomWriteTo,
MatchCondition("this.Method.Equals(o.Method)"),
MatchCondition("this.CompoundAssignmentType == o.CompoundAssignmentType"),
MatchCondition("Target.PerformMatch(o.Target, ref match)"),
MatchCondition("Value.PerformMatch(o.Value, ref match)")),
new OpCode("bit.not", "Bitwise NOT", Unary, CustomConstructor, MatchCondition("IsLifted == o.IsLifted && UnderlyingResultType == o.UnderlyingResultType")), new OpCode("bit.not", "Bitwise NOT", Unary, CustomConstructor, MatchCondition("IsLifted == o.IsLifted && UnderlyingResultType == o.UnderlyingResultType")),
new OpCode("arglist", "Retrieves the RuntimeArgumentHandle.", NoArguments, ResultType("O")), new OpCode("arglist", "Retrieves the RuntimeArgumentHandle.", NoArguments, ResultType("O")),
new OpCode("br", "Unconditional branch. <c>goto target;</c>", new OpCode("br", "Unconditional branch. <c>goto target;</c>",

94
ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs

@ -28,7 +28,35 @@ namespace ICSharpCode.Decompiler.IL
EvaluatesToNewValue EvaluatesToNewValue
} }
public partial class CompoundAssignmentInstruction : ILInstruction public abstract partial class CompoundAssignmentInstruction : ILInstruction
{
public readonly CompoundAssignmentType CompoundAssignmentType;
public CompoundAssignmentInstruction(OpCode opCode, CompoundAssignmentType compoundAssignmentType, ILInstruction target, ILInstruction value)
: base(opCode)
{
this.CompoundAssignmentType = compoundAssignmentType;
this.Target = target;
this.Value = value;
}
internal static bool IsValidCompoundAssignmentTarget(ILInstruction inst)
{
switch (inst.OpCode) {
// case OpCode.LdLoc: -- not valid -- does not mark the variable as written to
case OpCode.LdObj:
return true;
case OpCode.Call:
case OpCode.CallVirt:
var owner = ((CallInstruction)inst).Method.AccessorOwner as IProperty;
return owner != null && owner.CanSet;
default:
return false;
}
}
}
public partial class NumericCompoundAssign : CompoundAssignmentInstruction, ILiftableInstruction
{ {
/// <summary> /// <summary>
/// Gets whether the instruction checks for overflow. /// Gets whether the instruction checks for overflow.
@ -50,13 +78,11 @@ namespace ICSharpCode.Decompiler.IL
/// The operator used by this assignment operator instruction. /// The operator used by this assignment operator instruction.
/// </summary> /// </summary>
public readonly BinaryNumericOperator Operator; public readonly BinaryNumericOperator Operator;
public readonly CompoundAssignmentType CompoundAssignmentType;
public bool IsLifted { get; } public bool IsLifted { get; }
public CompoundAssignmentInstruction(BinaryNumericInstruction binary, ILInstruction target, ILInstruction value, IType type, CompoundAssignmentType compoundAssignmentType) public NumericCompoundAssign(BinaryNumericInstruction binary, ILInstruction target, ILInstruction value, IType type, CompoundAssignmentType compoundAssignmentType)
: base(OpCode.CompoundAssignmentInstruction) : base(OpCode.NumericCompoundAssign, compoundAssignmentType, target, value)
{ {
Debug.Assert(IsBinaryCompatibleWithType(binary, type)); Debug.Assert(IsBinaryCompatibleWithType(binary, type));
this.CheckForOverflow = binary.CheckForOverflow; this.CheckForOverflow = binary.CheckForOverflow;
@ -65,11 +91,8 @@ namespace ICSharpCode.Decompiler.IL
this.RightInputType = binary.RightInputType; this.RightInputType = binary.RightInputType;
this.UnderlyingResultType = binary.UnderlyingResultType; this.UnderlyingResultType = binary.UnderlyingResultType;
this.Operator = binary.Operator; this.Operator = binary.Operator;
this.CompoundAssignmentType = compoundAssignmentType;
this.IsLifted = binary.IsLifted; this.IsLifted = binary.IsLifted;
this.Target = target;
this.type = type; this.type = type;
this.Value = value;
this.ILRange = binary.ILRange; this.ILRange = binary.ILRange;
Debug.Assert(compoundAssignmentType == CompoundAssignmentType.EvaluatesToNewValue || (Operator == BinaryNumericOperator.Add || Operator == BinaryNumericOperator.Sub)); Debug.Assert(compoundAssignmentType == CompoundAssignmentType.EvaluatesToNewValue || (Operator == BinaryNumericOperator.Add || Operator == BinaryNumericOperator.Sub));
Debug.Assert(IsValidCompoundAssignmentTarget(Target)); Debug.Assert(IsValidCompoundAssignmentTarget(Target));
@ -85,7 +108,9 @@ namespace ICSharpCode.Decompiler.IL
return false; return false;
type = NullableType.GetUnderlyingType(type); type = NullableType.GetUnderlyingType(type);
} }
if (type.Kind == TypeKind.Enum) { if (type.Kind == TypeKind.Unknown) {
return false; // avoid introducing a potentially-incorrect compound assignment
} else if (type.Kind == TypeKind.Enum) {
switch (binary.Operator) { switch (binary.Operator) {
case BinaryNumericOperator.Add: case BinaryNumericOperator.Add:
case BinaryNumericOperator.Sub: case BinaryNumericOperator.Sub:
@ -127,24 +152,9 @@ namespace ICSharpCode.Decompiler.IL
return true; return true;
} }
internal static bool IsValidCompoundAssignmentTarget(ILInstruction inst)
{
switch (inst.OpCode) {
// case OpCode.LdLoc: -- not valid -- does not mark the variable as written to
case OpCode.LdObj:
return true;
case OpCode.Call:
case OpCode.CallVirt:
var owner = ((CallInstruction)inst).Method.AccessorOwner as IProperty;
return owner != null && owner.CanSet;
default:
return false;
}
}
protected override InstructionFlags ComputeFlags() protected override InstructionFlags ComputeFlags()
{ {
var flags = target.Flags | value.Flags | InstructionFlags.SideEffect; var flags = Target.Flags | Value.Flags | InstructionFlags.SideEffect;
if (CheckForOverflow || (Operator == BinaryNumericOperator.Div || Operator == BinaryNumericOperator.Rem)) if (CheckForOverflow || (Operator == BinaryNumericOperator.Div || Operator == BinaryNumericOperator.Rem))
flags |= InstructionFlags.MayThrow; flags |= InstructionFlags.MayThrow;
return flags; return flags;
@ -181,6 +191,40 @@ namespace ICSharpCode.Decompiler.IL
output.Write(')'); output.Write(')');
} }
} }
public partial class UserDefinedCompoundAssign : CompoundAssignmentInstruction
{
public readonly IMethod Method;
public bool IsLifted => false; // TODO: implement ILi
public UserDefinedCompoundAssign(IMethod method, CompoundAssignmentType compoundAssignmentType, ILInstruction target, ILInstruction value)
: base(OpCode.UserDefinedCompoundAssign, compoundAssignmentType, target, value)
{
this.Method = method;
Debug.Assert(Method.IsOperator);
Debug.Assert(compoundAssignmentType == CompoundAssignmentType.EvaluatesToNewValue || (Method.Name == "op_Increment" || Method.Name == "op_Decrement"));
Debug.Assert(IsValidCompoundAssignmentTarget(Target));
}
public override StackType ResultType => Method.ReturnType.GetStackType();
public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{
ILRange.WriteTo(output, options);
output.Write(OpCode);
if (CompoundAssignmentType == CompoundAssignmentType.EvaluatesToNewValue)
output.Write(".new");
else
output.Write(".old");
output.Write(' ');
Method.WriteTo(output);
output.Write('(');
this.Target.WriteTo(output, options);
output.Write(", ");
this.Value.WriteTo(output, options);
output.Write(')');
}
}
} }

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

@ -321,9 +321,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
} }
protected internal override void VisitCompoundAssignmentInstruction(CompoundAssignmentInstruction inst) protected internal override void VisitNumericCompoundAssign(NumericCompoundAssign inst)
{ {
base.VisitCompoundAssignmentInstruction(inst); base.VisitNumericCompoundAssign(inst);
if (inst.Target.MatchLdLoc(out var v)) { if (inst.Target.MatchLdLoc(out var v)) {
inst.ReplaceWith(new StLoc(v, new BinaryNumericInstruction(inst.Operator, inst.Target, inst.Value, inst.CheckForOverflow, inst.Sign))); inst.ReplaceWith(new StLoc(v, new BinaryNumericInstruction(inst.Operator, inst.Target, inst.Value, inst.CheckForOverflow, inst.Sign)));
} }

8
ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

@ -241,14 +241,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
expr.AcceptVisitor(this); expr.AcceptVisitor(this);
} else { } else {
base.VisitCall(inst); base.VisitCall(inst);
TransformAssignment.HandleCallCompoundAssign(inst, context); TransformAssignment.HandleCompoundAssign(inst, context);
} }
} }
protected internal override void VisitCallVirt(CallVirt inst) protected internal override void VisitCallVirt(CallVirt inst)
{ {
base.VisitCallVirt(inst); base.VisitCallVirt(inst);
TransformAssignment.HandleCallCompoundAssign(inst, context); TransformAssignment.HandleCompoundAssign(inst, context);
} }
protected internal override void VisitNewObj(NewObj inst) protected internal override void VisitNewObj(NewObj inst)
@ -301,8 +301,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
context.RequestRerun(); context.RequestRerun();
return; return;
} }
TransformAssignment.HandleStObjCompoundAssign(inst, context); TransformAssignment.HandleCompoundAssign(inst, context);
} }
protected internal override void VisitIfInstruction(IfInstruction inst) protected internal override void VisitIfInstruction(IfInstruction inst)
{ {

3
ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs

@ -451,7 +451,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
case OpCode.NewObj: case OpCode.NewObj:
case OpCode.StLoc: case OpCode.StLoc:
case OpCode.StObj: case OpCode.StObj:
case OpCode.CompoundAssignmentInstruction: case OpCode.NumericCompoundAssign:
case OpCode.UserDefinedCompoundAssign:
return true; return true;
default: default:
return false; return false;

3
ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

@ -296,7 +296,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
switch (inlinedExpression.OpCode) { switch (inlinedExpression.OpCode) {
case OpCode.DefaultValue: case OpCode.DefaultValue:
case OpCode.StObj: case OpCode.StObj:
case OpCode.CompoundAssignmentInstruction: case OpCode.NumericCompoundAssign:
case OpCode.UserDefinedCompoundAssign:
case OpCode.Await: case OpCode.Await:
return true; return true;
case OpCode.LdLoc: case OpCode.LdLoc:

23
ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs

@ -19,6 +19,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.FlowAnalysis; using ICSharpCode.Decompiler.FlowAnalysis;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.IL.Transforms namespace ICSharpCode.Decompiler.IL.Transforms
{ {
@ -27,6 +28,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// (=the initial value is a dead store). /// (=the initial value is a dead store).
/// ///
/// In yield return generators, additionally removes dead 'V = null;' assignments. /// In yield return generators, additionally removes dead 'V = null;' assignments.
///
/// Additionally infers IType of stack slots that have StackType.Ref
/// </summary> /// </summary>
public class RemoveDeadVariableInit : IILTransform public class RemoveDeadVariableInit : IILTransform
{ {
@ -66,6 +69,26 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
} }
} }
// Try to infer IType of stack slots that are of StackType.Ref:
foreach (var v in function.Variables) {
if (v.Kind == VariableKind.StackSlot && v.StackType == StackType.Ref && v.AddressCount == 0) {
IType newType = null;
// Multiple store are possible in case of (c ? ref a : ref b) += 1, for example.
foreach (var stloc in v.StoreInstructions.OfType<StLoc>()) {
var inferredType = stloc.Value.InferType();
// cancel, if types of values do not match exactly
if (newType != null && !newType.Equals(inferredType)) {
newType = SpecialType.UnknownType;
break;
}
newType = inferredType;
}
// Only overwrite existing type, if a "better" type was found.
if (newType != null && newType != SpecialType.UnknownType)
v.Type = newType;
}
}
} }
} }
} }

245
ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs

@ -31,7 +31,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
public class TransformAssignment : IStatementTransform public class TransformAssignment : IStatementTransform
{ {
StatementTransformContext context; StatementTransformContext context;
void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) void IStatementTransform.Run(Block block, int pos, StatementTransformContext context)
{ {
this.context = context; this.context = context;
@ -112,10 +112,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
if (!SemanticHelper.IsPure(stobj.Target.Flags) || inst.Variable.IsUsedWithin(stobj.Target)) if (!SemanticHelper.IsPure(stobj.Target.Flags) || inst.Variable.IsUsedWithin(stobj.Target))
return false; return false;
if (IsImplicitTruncation(inst.Value, stobj.Type)) { var newType = stobj.Target.InferType();
if (newType is ByReferenceType byref)
newType = byref.ElementType;
else if (newType is PointerType pointer)
newType = pointer.ElementType;
else
newType = stobj.Type;
if (IsImplicitTruncation(inst.Value, newType)) {
// 'stobj' is implicitly truncating the value // 'stobj' is implicitly truncating the value
return false; return false;
} }
stobj.Type = newType;
context.Step("Inline assignment stobj", stobj); context.Step("Inline assignment stobj", stobj);
block.Instructions.Remove(localStore); block.Instructions.Remove(localStore);
block.Instructions.Remove(stobj); block.Instructions.Remove(stobj);
@ -153,7 +161,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}; };
inst.ReplaceWith(new StLoc(local, inlineBlock)); inst.ReplaceWith(new StLoc(local, inlineBlock));
// because the ExpressionTransforms don't look into inline blocks, manually trigger HandleCallCompoundAssign // because the ExpressionTransforms don't look into inline blocks, manually trigger HandleCallCompoundAssign
if (HandleCallCompoundAssign(call, context)) { if (HandleCompoundAssign(call, context)) {
// if we did construct a compound assignment, it should have made our inline block redundant: // if we did construct a compound assignment, it should have made our inline block redundant:
if (inlineBlock.Instructions.Single().MatchStLoc(newVar, out var compoundAssign)) { if (inlineBlock.Instructions.Single().MatchStLoc(newVar, out var compoundAssign)) {
Debug.Assert(newVar.IsSingleDefinition && newVar.LoadCount == 1); Debug.Assert(newVar.IsSingleDefinition && newVar.LoadCount == 1);
@ -165,7 +173,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
} }
} }
static ILInstruction UnwrapSmallIntegerConv(ILInstruction inst, out Conv conv) static ILInstruction UnwrapSmallIntegerConv(ILInstruction inst, out Conv conv)
{ {
conv = inst as Conv; conv = inst as Conv;
@ -179,17 +187,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms
static bool ValidateCompoundAssign(BinaryNumericInstruction binary, Conv conv, IType targetType) static bool ValidateCompoundAssign(BinaryNumericInstruction binary, Conv conv, IType targetType)
{ {
if (!CompoundAssignmentInstruction.IsBinaryCompatibleWithType(binary, targetType)) if (!NumericCompoundAssign.IsBinaryCompatibleWithType(binary, targetType))
return false; return false;
if (conv != null && !(conv.TargetType == targetType.ToPrimitiveType() && conv.CheckForOverflow == binary.CheckForOverflow)) if (conv != null && !(conv.TargetType == targetType.ToPrimitiveType() && conv.CheckForOverflow == binary.CheckForOverflow))
return false; // conv does not match binary operation return false; // conv does not match binary operation
return true; return true;
} }
static bool MatchingGetterAndSetterCalls(CallInstruction getterCall, CallInstruction setterCall) static bool MatchingGetterAndSetterCalls(CallInstruction getterCall, CallInstruction setterCall)
{ {
if (getterCall == null || setterCall == null || !IsSameMember(getterCall.Method.AccessorOwner, setterCall.Method.AccessorOwner)) if (getterCall == null || setterCall == null || !IsSameMember(getterCall.Method.AccessorOwner, setterCall.Method.AccessorOwner))
return false; return false;
if (setterCall.OpCode != getterCall.OpCode)
return false;
var owner = getterCall.Method.AccessorOwner as IProperty; var owner = getterCall.Method.AccessorOwner as IProperty;
if (owner == null || !IsSameMember(getterCall.Method, owner.Getter) || !IsSameMember(setterCall.Method, owner.Setter)) if (owner == null || !IsSameMember(getterCall.Method, owner.Getter) || !IsSameMember(setterCall.Method, owner.Setter))
return false; return false;
@ -208,89 +218,93 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// <summary> /// <summary>
/// Transform compound assignments where the return value is not being used, /// Transform compound assignments where the return value is not being used,
/// or where there's an inlined assignment within the setter call. /// or where there's an inlined assignment within the setter call.
///
/// Patterns handled:
/// 1.
/// callvirt set_Property(ldloc S_1, binary.op(callvirt get_Property(ldloc S_1), value))
/// ==> compound.op.new(callvirt get_Property(ldloc S_1), value)
/// 2.
/// callvirt set_Property(ldloc S_1, stloc v(binary.op(callvirt get_Property(ldloc S_1), value)))
/// ==> stloc v(compound.op.new(callvirt get_Property(ldloc S_1), value))
/// 3.
/// stobj(target, binary.op(ldobj(target), ...))
/// where target is pure
/// => compound.op(target, ...)
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Called by ExpressionTransforms. /// Called by ExpressionTransforms, or after the inline-assignment transform for setters.
/// </remarks> /// </remarks>
internal static bool HandleCallCompoundAssign(CallInstruction setterCall, StatementTransformContext context) internal static bool HandleCompoundAssign(ILInstruction compoundStore, StatementTransformContext context)
{ {
// callvirt set_Property(ldloc S_1, binary.op(callvirt get_Property(ldloc S_1), value)) if (compoundStore is CallInstruction && compoundStore.SlotInfo != Block.InstructionSlot) {
// ==> compound.op.new(callvirt get_Property(ldloc S_1), value) // replacing 'call set_Property' with a compound assignment instruction
var setterValue = setterCall.Arguments.LastOrDefault(); // changes the return value of the expression, so this is only valid on block-level.
return false;
}
if (!IsCompoundStore(compoundStore, out var targetType, out var setterValue))
return false;
// targetType = The type of the property/field/etc. being stored to.
// setterValue = The value being stored.
var storeInSetter = setterValue as StLoc; var storeInSetter = setterValue as StLoc;
if (storeInSetter != null) { if (storeInSetter != null) {
// We'll move the stloc to top-level:
// callvirt set_Property(ldloc S_1, stloc v(binary.op(callvirt get_Property(ldloc S_1), value))) // callvirt set_Property(ldloc S_1, stloc v(binary.op(callvirt get_Property(ldloc S_1), value)))
// ==> stloc v(compound.op.new(callvirt get_Property(ldloc S_1), value)) // ==> stloc v(compound.op.new(callvirt get_Property(ldloc S_1), value))
setterValue = storeInSetter.Value; setterValue = storeInSetter.Value;
if (storeInSetter.Variable.Type.IsSmallIntegerType()) {
// 'stloc v' implicitly truncates the value.
// Ensure that type of 'v' matches the type of the property:
if (storeInSetter.Variable.Type.GetSize() != targetType.GetSize())
return false;
if (storeInSetter.Variable.Type.GetSign() != targetType.GetSign())
return false;
}
} }
setterValue = UnwrapSmallIntegerConv(setterValue, out var conv); ILInstruction newInst;
if (!(setterValue is BinaryNumericInstruction binary)) if (UnwrapSmallIntegerConv(setterValue, out var smallIntConv) is BinaryNumericInstruction binary) {
return false; if (!IsMatchingCompoundLoad(binary.Left, compoundStore, forbiddenVariable: storeInSetter?.Variable))
var getterCall = binary.Left as CallInstruction; return false;
if (!MatchingGetterAndSetterCalls(getterCall, setterCall)) if (!ValidateCompoundAssign(binary, smallIntConv, targetType))
return false;
IType targetType = getterCall.Method.ReturnType;
if (!ValidateCompoundAssign(binary, conv, targetType))
return false;
if (storeInSetter != null && storeInSetter.Variable.Type.IsSmallIntegerType()) {
// 'stloc v' implicitly truncates.
// Ensure that type of 'v' must match type of the property:
if (storeInSetter.Variable.Type.GetSize() != targetType.GetSize())
return false; return false;
if (storeInSetter.Variable.Type.GetSign() != targetType.GetSign()) context.Step($"Compound assignment (binary.numeric)", compoundStore);
newInst = new NumericCompoundAssign(
binary, binary.Left, binary.Right,
targetType, CompoundAssignmentType.EvaluatesToNewValue);
} else if (setterValue is Call operatorCall && operatorCall.Method.IsOperator) {
if (operatorCall.Arguments.Count == 0)
return false; return false;
if (!IsMatchingCompoundLoad(operatorCall.Arguments[0], compoundStore, forbiddenVariable: storeInSetter?.Variable))
return false;
ILInstruction rhs;
if (operatorCall.Arguments.Count == 2) {
if (CSharp.ExpressionBuilder.GetAssignmentOperatorTypeFromMetadataName(operatorCall.Method.Name) == null)
return false;
rhs = operatorCall.Arguments[1];
} else if (operatorCall.Arguments.Count == 1) {
if (!(operatorCall.Method.Name == "op_Increment" || operatorCall.Method.Name == "op_Decrement"))
return false;
// use a dummy node so that we don't need a dedicated instruction for user-defined unary operator calls
rhs = new LdcI4(1);
} else {
return false;
}
if (operatorCall.IsLifted)
return false; // TODO: add tests and think about whether nullables need special considerations
context.Step($"Compound assignment (user-defined binary)", compoundStore);
newInst = new UserDefinedCompoundAssign(operatorCall.Method, CompoundAssignmentType.EvaluatesToNewValue,
operatorCall.Arguments[0], rhs);
} else {
return false;
} }
context.Step($"Compound assignment to '{getterCall.Method.AccessorOwner.Name}'", setterCall);
ILInstruction newInst = new CompoundAssignmentInstruction(
binary, getterCall, binary.Right,
getterCall.Method.ReturnType, CompoundAssignmentType.EvaluatesToNewValue);
if (storeInSetter != null) { if (storeInSetter != null) {
storeInSetter.Value = newInst; storeInSetter.Value = newInst;
newInst = storeInSetter; newInst = storeInSetter;
context.RequestRerun(); // moving stloc to top-level might trigger inlining context.RequestRerun(); // moving stloc to top-level might trigger inlining
} }
setterCall.ReplaceWith(newInst); compoundStore.ReplaceWith(newInst);
return true; return true;
} }
/// <summary>
/// stobj(target, binary.op(ldobj(target), ...))
/// where target is pure
/// => compound.op(target, ...)
/// </summary>
/// <remarks>
/// Called by ExpressionTransforms.
/// </remarks>
internal static bool HandleStObjCompoundAssign(StObj inst, ILTransformContext context)
{
if (!(UnwrapSmallIntegerConv(inst.Value, out var conv) is BinaryNumericInstruction binary))
return false;
if (!(binary.Left is LdObj ldobj))
return false;
if (!inst.Target.Match(ldobj.Target).Success)
return false;
if (!SemanticHelper.IsPure(ldobj.Target.Flags))
return false;
// ldobj.Type may just be 'int' (due to ldind.i4) when we're actually operating on a 'ref MyEnum'.
// Try to determine the real type of the object we're modifying:
IType targetType = ldobj.Target.InferType();
if (targetType.Kind == TypeKind.Pointer || targetType.Kind == TypeKind.ByReference) {
targetType = ((TypeWithElementType)targetType).ElementType;
if (targetType.Kind == TypeKind.Unknown || targetType.GetSize() != ldobj.Type.GetSize()) {
targetType = ldobj.Type;
}
} else {
targetType = ldobj.Type;
}
if (!ValidateCompoundAssign(binary, conv, targetType))
return false;
context.Step("compound assignment", inst);
inst.ReplaceWith(new CompoundAssignmentInstruction(
binary, binary.Left, binary.Right,
targetType, CompoundAssignmentType.EvaluatesToNewValue));
return true;
}
/// <code> /// <code>
/// stloc s(value) /// stloc s(value)
/// stloc l(ldloc s) /// stloc l(ldloc s)
@ -375,7 +389,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
return true; return true;
} }
/// <code> /// <code>
/// stloc s(ldloc l) /// stloc s(ldloc l)
/// stloc l(binary.op(ldloc s, ldc.i4 1)) /// stloc l(binary.op(ldloc s, ldc.i4 1))
@ -413,16 +427,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms
block.Instructions.RemoveAt(pos + 1); // remove nextInst block.Instructions.RemoveAt(pos + 1); // remove nextInst
return true; return true;
} }
/// <summary> /// <summary>
/// Gets whether 'inst' is a possible store for use as a compound store. /// Gets whether 'inst' is a possible store for use as a compound store.
/// </summary> /// </summary>
bool IsCompoundStore(ILInstruction inst, out IType storeType, out ILInstruction value) static bool IsCompoundStore(ILInstruction inst, out IType storeType, out ILInstruction value)
{ {
value = null; value = null;
storeType = null; storeType = null;
if (inst is StObj stobj) { if (inst is StObj stobj) {
storeType = stobj.Type; // stobj.Type may just be 'int' (due to stind.i4) when we're actually operating on a 'ref MyEnum'.
// Try to determine the real type of the object we're modifying:
storeType = stobj.Target.InferType();
if (storeType is ByReferenceType refType) {
storeType = refType.ElementType;
} else if (storeType is PointerType pointerType) {
storeType = pointerType.ElementType;
} else {
storeType = stobj.Type;
}
value = stobj.Value; value = stobj.Value;
return SemanticHelper.IsPure(stobj.Target.Flags); return SemanticHelper.IsPure(stobj.Target.Flags);
} else if (inst is CallInstruction call && (call.OpCode == OpCode.Call || call.OpCode == OpCode.CallVirt)) { } else if (inst is CallInstruction call && (call.OpCode == OpCode.Call || call.OpCode == OpCode.CallVirt)) {
@ -442,17 +465,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
} }
bool IsMatchingCompoundLoad(ILInstruction load, ILInstruction store, ILVariable forbiddenVariable) static bool IsMatchingCompoundLoad(ILInstruction load, ILInstruction store, ILVariable forbiddenVariable)
{ {
if (load is LdObj ldobj && store is StObj stobj) { if (load is LdObj ldobj && store is StObj stobj) {
Debug.Assert(SemanticHelper.IsPure(stobj.Target.Flags)); Debug.Assert(SemanticHelper.IsPure(stobj.Target.Flags));
if (!SemanticHelper.IsPure(ldobj.Target.Flags)) if (!SemanticHelper.IsPure(ldobj.Target.Flags))
return false; return false;
if (forbiddenVariable.IsUsedWithin(ldobj.Target)) if (forbiddenVariable != null && forbiddenVariable.IsUsedWithin(ldobj.Target))
return false; return false;
return ldobj.Target.Match(stobj.Target).Success; return ldobj.Target.Match(stobj.Target).Success;
} else if (MatchingGetterAndSetterCalls(load as CallInstruction, store as CallInstruction)) { } else if (MatchingGetterAndSetterCalls(load as CallInstruction, store as CallInstruction)) {
if (forbiddenVariable.IsUsedWithin(load)) if (forbiddenVariable != null && forbiddenVariable.IsUsedWithin(load))
return false; return false;
return true; return true;
} else { } else {
@ -474,6 +497,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// stloc l(compound.op.old(call get_Prop(target), ldc.i4 1)) /// stloc l(compound.op.old(call get_Prop(target), ldc.i4 1))
/// </code> /// </code>
/// <remarks> /// <remarks>
/// This pattern is used for post-increment by legacy csc.
///
/// Even though this transform operates only on a single expression, it's not an expression transform /// Even though this transform operates only on a single expression, it's not an expression transform
/// as the result value of the expression changes (this is OK only for statements in a block). /// as the result value of the expression changes (this is OK only for statements in a block).
/// </remarks> /// </remarks>
@ -482,24 +507,40 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var store = block.Instructions[pos]; var store = block.Instructions[pos];
if (!IsCompoundStore(store, out var targetType, out var value)) if (!IsCompoundStore(store, out var targetType, out var value))
return false; return false;
StLoc stloc;
var binary = UnwrapSmallIntegerConv(value, out var conv) as BinaryNumericInstruction; var binary = UnwrapSmallIntegerConv(value, out var conv) as BinaryNumericInstruction;
if (binary == null || !binary.Right.MatchLdcI4(1)) if (binary != null && binary.Right.MatchLdcI(1)) {
return false; if (!(binary.Operator == BinaryNumericOperator.Add || binary.Operator == BinaryNumericOperator.Sub))
if (!(binary.Operator == BinaryNumericOperator.Add || binary.Operator == BinaryNumericOperator.Sub)) return false;
if (!ValidateCompoundAssign(binary, conv, targetType))
return false;
stloc = binary.Left as StLoc;
} else if (value is Call operatorCall && operatorCall.Method.IsOperator && operatorCall.Arguments.Count == 1) {
if (!(operatorCall.Method.Name == "op_Increment" || operatorCall.Method.Name == "op_Decrement"))
return false;
if (operatorCall.IsLifted)
return false; // TODO: add tests and think about whether nullables need special considerations
stloc = operatorCall.Arguments[0] as StLoc;
} else {
return false; return false;
if (!(binary.Left is StLoc stloc)) }
if (stloc == null)
return false; return false;
if (!(stloc.Variable.Kind == VariableKind.Local || stloc.Variable.Kind == VariableKind.StackSlot)) if (!(stloc.Variable.Kind == VariableKind.Local || stloc.Variable.Kind == VariableKind.StackSlot))
return false; return false;
if (!IsMatchingCompoundLoad(stloc.Value, store, stloc.Variable)) if (!IsMatchingCompoundLoad(stloc.Value, store, stloc.Variable))
return false; return false;
if (!ValidateCompoundAssign(binary, conv, targetType))
return false;
if (IsImplicitTruncation(stloc.Value, stloc.Variable.Type)) if (IsImplicitTruncation(stloc.Value, stloc.Variable.Type))
return false; return false;
context.Step("TransformPostIncDecOperatorWithInlineStore", store); context.Step("TransformPostIncDecOperatorWithInlineStore", store);
block.Instructions[pos] = new StLoc(stloc.Variable, new CompoundAssignmentInstruction( if (binary != null) {
binary, stloc.Value, binary.Right, targetType, CompoundAssignmentType.EvaluatesToOldValue)); block.Instructions[pos] = new StLoc(stloc.Variable, new NumericCompoundAssign(
binary, stloc.Value, binary.Right, targetType, CompoundAssignmentType.EvaluatesToOldValue));
} else {
Call operatorCall = (Call)value;
block.Instructions[pos] = new StLoc(stloc.Variable, new UserDefinedCompoundAssign(
operatorCall.Method, CompoundAssignmentType.EvaluatesToOldValue, stloc.Value, new LdcI4(1)));
}
return true; return true;
} }
@ -521,19 +562,35 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
if (!IsCompoundStore(store, out var targetType, out var value)) if (!IsCompoundStore(store, out var targetType, out var value))
return false; return false;
if (!IsMatchingCompoundLoad(inst.Value, store, inst.Variable)) if (IsImplicitTruncation(inst.Value, targetType)) {
// 'stloc l' is implicitly truncating the value
return false; return false;
var binary = UnwrapSmallIntegerConv(value, out var conv) as BinaryNumericInstruction; }
if (binary == null || !binary.Left.MatchLdLoc(inst.Variable) || !binary.Right.MatchLdcI4(1)) if (!IsMatchingCompoundLoad(inst.Value, store, inst.Variable))
return false;
if (!(binary.Operator == BinaryNumericOperator.Add || binary.Operator == BinaryNumericOperator.Sub))
return false;
if (!ValidateCompoundAssign(binary, conv, targetType))
return false; return false;
if (IsImplicitTruncation(value, targetType)) if (UnwrapSmallIntegerConv(value, out var conv) is BinaryNumericInstruction binary) {
if (!binary.Left.MatchLdLoc(inst.Variable) || !binary.Right.MatchLdcI(1))
return false;
if (!(binary.Operator == BinaryNumericOperator.Add || binary.Operator == BinaryNumericOperator.Sub))
return false;
if (!ValidateCompoundAssign(binary, conv, targetType))
return false;
context.Step("TransformPostIncDecOperator (builtin)", inst);
inst.Value = new NumericCompoundAssign(binary, inst.Value, binary.Right,
targetType, CompoundAssignmentType.EvaluatesToOldValue);
} else if (value is Call operatorCall && operatorCall.Method.IsOperator && operatorCall.Arguments.Count == 1) {
if (!operatorCall.Arguments[0].MatchLdLoc(inst.Variable))
return false;
if (!(operatorCall.Method.Name == "op_Increment" || operatorCall.Method.Name == "op_Decrement"))
return false;
if (operatorCall.IsLifted)
return false; // TODO: add tests and think about whether nullables need special considerations
context.Step("TransformPostIncDecOperator (user-defined)", inst);
inst.Value = new UserDefinedCompoundAssign(operatorCall.Method,
CompoundAssignmentType.EvaluatesToOldValue, inst.Value, new LdcI4(1));
} else {
return false; return false;
context.Step("TransformPostIncDecOperator", inst); }
inst.Value = new CompoundAssignmentInstruction(binary, inst.Value, binary.Right, targetType, CompoundAssignmentType.EvaluatesToOldValue);
block.Instructions.RemoveAt(i + 1); block.Instructions.RemoveAt(i + 1);
if (inst.Variable.IsSingleDefinition && inst.Variable.LoadCount == 0) { if (inst.Variable.IsSingleDefinition && inst.Variable.LoadCount == 0) {
// dead store -> it was a statement-level post-increment // dead store -> it was a statement-level post-increment

Loading…
Cancel
Save