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 @@ -1102,8 +1102,69 @@ namespace ICSharpCode.Decompiler.CSharp
.WithILInstruction(inst)
.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) {
case BinaryNumericOperator.Add:
@ -1131,7 +1192,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -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 value = Translate(inst.Value);
@ -1196,7 +1257,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1196,7 +1257,7 @@ namespace ICSharpCode.Decompiler.CSharp
return resultExpr;
}
TranslatedExpression HandleCompoundShift(CompoundAssignmentInstruction inst, AssignmentOperatorType op)
TranslatedExpression HandleCompoundShift(NumericCompoundAssign inst, AssignmentOperatorType op)
{
Debug.Assert(inst.CompoundAssignmentType == CompoundAssignmentType.EvaluatesToNewValue);
var target = Translate(inst.Target);
@ -1714,7 +1775,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1714,7 +1775,7 @@ namespace ICSharpCode.Decompiler.CSharp
{
TranslatedExpression arrayExpr = Translate(inst.Array);
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);
arrayExpr = arrayExpr.ConvertTo(arrayType, this);
}

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

@ -16,9 +16,12 @@ @@ -16,9 +16,12 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Linq;
using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.CSharp.Transforms
{
@ -52,7 +55,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -52,7 +55,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
// TODO: context.Settings.IntroduceIncrementAndDecrement
if (assignment.Operator == AssignmentOperatorType.Add || assignment.Operator == AssignmentOperatorType.Subtract) {
// 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
if (assignment.Annotation<IL.CallInstruction>() == null) {
UnaryOperatorType type;

8
ICSharpCode.Decompiler/IL/ILTypeExtensions.cs

@ -163,6 +163,14 @@ namespace ICSharpCode.Decompiler.IL @@ -163,6 +163,14 @@ namespace ICSharpCode.Decompiler.IL
return new TypeSystem.ByReferenceType(ldflda.Field.Type);
case LdsFlda ldsflda:
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:
return SpecialType.UnknownType;
}

219
ICSharpCode.Decompiler/IL/Instructions.cs

@ -45,8 +45,10 @@ namespace ICSharpCode.Decompiler.IL @@ -45,8 +45,10 @@ namespace ICSharpCode.Decompiler.IL
PinnedRegion,
/// <summary>Common instruction for add, sub, mul, div, rem, bit.and, bit.or, bit.xor, shl and shr.</summary>
BinaryNumericInstruction,
/// <summary>Common instruction for compound assignments.</summary>
CompoundAssignmentInstruction,
/// <summary>Common instruction for numeric compound assignments.</summary>
NumericCompoundAssign,
/// <summary>Common instruction for user-defined compound assignments.</summary>
UserDefinedCompoundAssign,
/// <summary>Bitwise NOT</summary>
BitNot,
/// <summary>Retrieves the RuntimeArgumentHandle.</summary>
@ -485,6 +487,96 @@ namespace ICSharpCode.Decompiler.IL.Patterns @@ -485,6 +487,96 @@ namespace ICSharpCode.Decompiler.IL.Patterns
}
}
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>
public sealed partial class InvalidBranch : SimpleInstruction
@ -891,96 +983,66 @@ namespace ICSharpCode.Decompiler.IL @@ -891,96 +983,66 @@ namespace ICSharpCode.Decompiler.IL
}
namespace ICSharpCode.Decompiler.IL
{
/// <summary>Common instruction for compound assignments.</summary>
public sealed partial class CompoundAssignmentInstruction : ILInstruction
/// <summary>Common instruction for numeric compound assignments.</summary>
public sealed partial class NumericCompoundAssign : CompoundAssignmentInstruction
{
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);
}
IType type;
/// <summary>Returns the type operand.</summary>
public IType Type {
get { return type; }
set { type = value; InvalidateFlags(); }
}
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) {
case 0:
return this.target;
case 1:
return this.value;
default:
throw new IndexOutOfRangeException();
}
return visitor.VisitNumericCompoundAssign(this);
}
protected sealed override void SetChild(int index, ILInstruction value)
public override T AcceptVisitor<C, T>(ILVisitor<C, T> visitor, C context)
{
switch (index) {
case 0:
this.Target = value;
break;
case 1:
this.Value = value;
break;
default:
throw new IndexOutOfRangeException();
}
return visitor.VisitNumericCompoundAssign(this, context);
}
protected sealed override SlotInfo GetChildSlot(int index)
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{
switch (index) {
case 0:
return TargetSlot;
case 1:
return ValueSlot;
default:
throw new IndexOutOfRangeException();
}
var o = other as NumericCompoundAssign;
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);
}
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();
clone.Target = this.target.Clone();
clone.Value = this.value.Clone();
return clone;
return base.ComputeFlags() | InstructionFlags.MayThrow | InstructionFlags.SideEffect;
}
IType type;
/// <summary>Returns the type operand.</summary>
public IType Type {
get { return type; }
set { type = value; InvalidateFlags(); }
public override InstructionFlags DirectFlags {
get {
return base.DirectFlags | InstructionFlags.MayThrow | InstructionFlags.SideEffect;
}
}
public override StackType ResultType { get { return type.GetStackType(); } }
public override void AcceptVisitor(ILVisitor visitor)
{
visitor.VisitCompoundAssignmentInstruction(this);
visitor.VisitUserDefinedCompoundAssign(this);
}
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)
{
return visitor.VisitCompoundAssignmentInstruction(this, context);
return visitor.VisitUserDefinedCompoundAssign(this, context);
}
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{
var o = other as CompoundAssignmentInstruction;
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;
var o = other as UserDefinedCompoundAssign;
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 @@ -5099,7 +5161,11 @@ namespace ICSharpCode.Decompiler.IL
{
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);
}
@ -5417,7 +5483,11 @@ namespace ICSharpCode.Decompiler.IL @@ -5417,7 +5483,11 @@ namespace ICSharpCode.Decompiler.IL
{
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);
}
@ -5735,7 +5805,11 @@ namespace ICSharpCode.Decompiler.IL @@ -5735,7 +5805,11 @@ namespace ICSharpCode.Decompiler.IL
{
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);
}
@ -6024,7 +6098,8 @@ namespace ICSharpCode.Decompiler.IL @@ -6024,7 +6098,8 @@ namespace ICSharpCode.Decompiler.IL
"Block",
"PinnedRegion",
"binary",
"compound",
"numeric.compound",
"user.compound",
"bit.not",
"arglist",
"br",

21
ICSharpCode.Decompiler/IL/Instructions.tt

@ -33,7 +33,9 @@ @@ -33,7 +33,9 @@
new OpCode("CallInstruction", "Instruction with a list of arguments.",
AbstractBaseClass, CustomChildren(new []{ new ArgumentInfo("arguments") { IsCollection = true }}),
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 = {
@ -66,10 +68,19 @@ @@ -66,10 +68,19 @@
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,
MatchCondition("CheckForOverflow == o.CheckForOverflow && Sign == o.Sign && Operator == o.Operator && IsLifted == o.IsLifted")),
new OpCode("compound", "Common instruction for compound assignments.",
CustomClassName("CompoundAssignmentInstruction"), CustomConstructor, CustomComputeFlags,
MayThrow, CustomArguments(("target", null), ("value", null)), HasTypeOperand, ResultType("type.GetStackType()"), CustomWriteTo,
MatchCondition("CheckForOverflow == o.CheckForOverflow && Sign == o.Sign && Operator == o.Operator")),
new OpCode("numeric.compound", "Common instruction for numeric compound assignments.",
CustomClassName("NumericCompoundAssign"), BaseClass("CompoundAssignmentInstruction"), CustomConstructor, CustomComputeFlags,
MayThrow, HasTypeOperand, ResultType("type.GetStackType()"), CustomWriteTo,
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("arglist", "Retrieves the RuntimeArgumentHandle.", NoArguments, ResultType("O")),
new OpCode("br", "Unconditional branch. <c>goto target;</c>",

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

@ -28,7 +28,35 @@ namespace ICSharpCode.Decompiler.IL @@ -28,7 +28,35 @@ namespace ICSharpCode.Decompiler.IL
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>
/// Gets whether the instruction checks for overflow.
@ -50,13 +78,11 @@ namespace ICSharpCode.Decompiler.IL @@ -50,13 +78,11 @@ namespace ICSharpCode.Decompiler.IL
/// The operator used by this assignment operator instruction.
/// </summary>
public readonly BinaryNumericOperator Operator;
public readonly CompoundAssignmentType CompoundAssignmentType;
public bool IsLifted { get; }
public CompoundAssignmentInstruction(BinaryNumericInstruction binary, ILInstruction target, ILInstruction value, IType type, CompoundAssignmentType compoundAssignmentType)
: base(OpCode.CompoundAssignmentInstruction)
public NumericCompoundAssign(BinaryNumericInstruction binary, ILInstruction target, ILInstruction value, IType type, CompoundAssignmentType compoundAssignmentType)
: base(OpCode.NumericCompoundAssign, compoundAssignmentType, target, value)
{
Debug.Assert(IsBinaryCompatibleWithType(binary, type));
this.CheckForOverflow = binary.CheckForOverflow;
@ -65,11 +91,8 @@ namespace ICSharpCode.Decompiler.IL @@ -65,11 +91,8 @@ namespace ICSharpCode.Decompiler.IL
this.RightInputType = binary.RightInputType;
this.UnderlyingResultType = binary.UnderlyingResultType;
this.Operator = binary.Operator;
this.CompoundAssignmentType = compoundAssignmentType;
this.IsLifted = binary.IsLifted;
this.Target = target;
this.type = type;
this.Value = value;
this.ILRange = binary.ILRange;
Debug.Assert(compoundAssignmentType == CompoundAssignmentType.EvaluatesToNewValue || (Operator == BinaryNumericOperator.Add || Operator == BinaryNumericOperator.Sub));
Debug.Assert(IsValidCompoundAssignmentTarget(Target));
@ -85,7 +108,9 @@ namespace ICSharpCode.Decompiler.IL @@ -85,7 +108,9 @@ namespace ICSharpCode.Decompiler.IL
return false;
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) {
case BinaryNumericOperator.Add:
case BinaryNumericOperator.Sub:
@ -127,24 +152,9 @@ namespace ICSharpCode.Decompiler.IL @@ -127,24 +152,9 @@ namespace ICSharpCode.Decompiler.IL
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()
{
var flags = target.Flags | value.Flags | InstructionFlags.SideEffect;
var flags = Target.Flags | Value.Flags | InstructionFlags.SideEffect;
if (CheckForOverflow || (Operator == BinaryNumericOperator.Div || Operator == BinaryNumericOperator.Rem))
flags |= InstructionFlags.MayThrow;
return flags;
@ -181,6 +191,40 @@ namespace ICSharpCode.Decompiler.IL @@ -181,6 +191,40 @@ namespace ICSharpCode.Decompiler.IL
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 @@ -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)) {
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 @@ -241,14 +241,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
expr.AcceptVisitor(this);
} else {
base.VisitCall(inst);
TransformAssignment.HandleCallCompoundAssign(inst, context);
TransformAssignment.HandleCompoundAssign(inst, context);
}
}
protected internal override void VisitCallVirt(CallVirt inst)
{
base.VisitCallVirt(inst);
TransformAssignment.HandleCallCompoundAssign(inst, context);
TransformAssignment.HandleCompoundAssign(inst, context);
}
protected internal override void VisitNewObj(NewObj inst)
@ -301,8 +301,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -301,8 +301,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
context.RequestRerun();
return;
}
TransformAssignment.HandleStObjCompoundAssign(inst, context);
}
TransformAssignment.HandleCompoundAssign(inst, context);
}
protected internal override void VisitIfInstruction(IfInstruction inst)
{

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

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

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

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

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

@ -19,6 +19,7 @@ @@ -19,6 +19,7 @@
using System.Collections.Generic;
using System.Linq;
using ICSharpCode.Decompiler.FlowAnalysis;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.IL.Transforms
{
@ -27,6 +28,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -27,6 +28,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// (=the initial value is a dead store).
///
/// In yield return generators, additionally removes dead 'V = null;' assignments.
///
/// Additionally infers IType of stack slots that have StackType.Ref
/// </summary>
public class RemoveDeadVariableInit : IILTransform
{
@ -66,6 +69,26 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -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 @@ -31,7 +31,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
public class TransformAssignment : IStatementTransform
{
StatementTransformContext context;
void IStatementTransform.Run(Block block, int pos, StatementTransformContext context)
{
this.context = context;
@ -112,10 +112,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -112,10 +112,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (!SemanticHelper.IsPure(stobj.Target.Flags) || inst.Variable.IsUsedWithin(stobj.Target))
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
return false;
}
stobj.Type = newType;
context.Step("Inline assignment stobj", stobj);
block.Instructions.Remove(localStore);
block.Instructions.Remove(stobj);
@ -153,7 +161,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -153,7 +161,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
};
inst.ReplaceWith(new StLoc(local, inlineBlock));
// 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 (inlineBlock.Instructions.Single().MatchStLoc(newVar, out var compoundAssign)) {
Debug.Assert(newVar.IsSingleDefinition && newVar.LoadCount == 1);
@ -165,7 +173,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -165,7 +173,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
}
}
static ILInstruction UnwrapSmallIntegerConv(ILInstruction inst, out Conv conv)
{
conv = inst as Conv;
@ -179,17 +187,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -179,17 +187,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms
static bool ValidateCompoundAssign(BinaryNumericInstruction binary, Conv conv, IType targetType)
{
if (!CompoundAssignmentInstruction.IsBinaryCompatibleWithType(binary, targetType))
if (!NumericCompoundAssign.IsBinaryCompatibleWithType(binary, targetType))
return false;
if (conv != null && !(conv.TargetType == targetType.ToPrimitiveType() && conv.CheckForOverflow == binary.CheckForOverflow))
return false; // conv does not match binary operation
return true;
}
static bool MatchingGetterAndSetterCalls(CallInstruction getterCall, CallInstruction setterCall)
{
if (getterCall == null || setterCall == null || !IsSameMember(getterCall.Method.AccessorOwner, setterCall.Method.AccessorOwner))
return false;
if (setterCall.OpCode != getterCall.OpCode)
return false;
var owner = getterCall.Method.AccessorOwner as IProperty;
if (owner == null || !IsSameMember(getterCall.Method, owner.Getter) || !IsSameMember(setterCall.Method, owner.Setter))
return false;
@ -208,89 +218,93 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -208,89 +218,93 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// <summary>
/// Transform compound assignments where the return value is not being used,
/// 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>
/// <remarks>
/// Called by ExpressionTransforms.
/// Called by ExpressionTransforms, or after the inline-assignment transform for setters.
/// </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))
// ==> compound.op.new(callvirt get_Property(ldloc S_1), value)
var setterValue = setterCall.Arguments.LastOrDefault();
if (compoundStore is CallInstruction && compoundStore.SlotInfo != Block.InstructionSlot) {
// replacing 'call set_Property' with a compound assignment instruction
// 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;
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)))
// ==> stloc v(compound.op.new(callvirt get_Property(ldloc S_1), 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);
if (!(setterValue is BinaryNumericInstruction binary))
return false;
var getterCall = binary.Left as CallInstruction;
if (!MatchingGetterAndSetterCalls(getterCall, setterCall))
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())
ILInstruction newInst;
if (UnwrapSmallIntegerConv(setterValue, out var smallIntConv) is BinaryNumericInstruction binary) {
if (!IsMatchingCompoundLoad(binary.Left, compoundStore, forbiddenVariable: storeInSetter?.Variable))
return false;
if (!ValidateCompoundAssign(binary, smallIntConv, targetType))
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;
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) {
storeInSetter.Value = newInst;
newInst = storeInSetter;
context.RequestRerun(); // moving stloc to top-level might trigger inlining
}
setterCall.ReplaceWith(newInst);
compoundStore.ReplaceWith(newInst);
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>
/// stloc s(value)
/// stloc l(ldloc s)
@ -375,7 +389,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -375,7 +389,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
return true;
}
/// <code>
/// stloc s(ldloc l)
/// stloc l(binary.op(ldloc s, ldc.i4 1))
@ -413,16 +427,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -413,16 +427,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms
block.Instructions.RemoveAt(pos + 1); // remove nextInst
return true;
}
/// <summary>
/// Gets whether 'inst' is a possible store for use as a compound store.
/// </summary>
bool IsCompoundStore(ILInstruction inst, out IType storeType, out ILInstruction value)
static bool IsCompoundStore(ILInstruction inst, out IType storeType, out ILInstruction value)
{
value = null;
storeType = null;
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;
return SemanticHelper.IsPure(stobj.Target.Flags);
} else if (inst is CallInstruction call && (call.OpCode == OpCode.Call || call.OpCode == OpCode.CallVirt)) {
@ -442,17 +465,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -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) {
Debug.Assert(SemanticHelper.IsPure(stobj.Target.Flags));
if (!SemanticHelper.IsPure(ldobj.Target.Flags))
return false;
if (forbiddenVariable.IsUsedWithin(ldobj.Target))
if (forbiddenVariable != null && forbiddenVariable.IsUsedWithin(ldobj.Target))
return false;
return ldobj.Target.Match(stobj.Target).Success;
} else if (MatchingGetterAndSetterCalls(load as CallInstruction, store as CallInstruction)) {
if (forbiddenVariable.IsUsedWithin(load))
if (forbiddenVariable != null && forbiddenVariable.IsUsedWithin(load))
return false;
return true;
} else {
@ -474,6 +497,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -474,6 +497,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// stloc l(compound.op.old(call get_Prop(target), ldc.i4 1))
/// </code>
/// <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
/// as the result value of the expression changes (this is OK only for statements in a block).
/// </remarks>
@ -482,24 +507,40 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -482,24 +507,40 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var store = block.Instructions[pos];
if (!IsCompoundStore(store, out var targetType, out var value))
return false;
StLoc stloc;
var binary = UnwrapSmallIntegerConv(value, out var conv) as BinaryNumericInstruction;
if (binary == null || !binary.Right.MatchLdcI4(1))
return false;
if (!(binary.Operator == BinaryNumericOperator.Add || binary.Operator == BinaryNumericOperator.Sub))
if (binary != null && binary.Right.MatchLdcI(1)) {
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;
if (!(binary.Left is StLoc stloc))
}
if (stloc == null)
return false;
if (!(stloc.Variable.Kind == VariableKind.Local || stloc.Variable.Kind == VariableKind.StackSlot))
return false;
if (!IsMatchingCompoundLoad(stloc.Value, store, stloc.Variable))
return false;
if (!ValidateCompoundAssign(binary, conv, targetType))
return false;
if (IsImplicitTruncation(stloc.Value, stloc.Variable.Type))
return false;
context.Step("TransformPostIncDecOperatorWithInlineStore", store);
block.Instructions[pos] = new StLoc(stloc.Variable, new CompoundAssignmentInstruction(
binary, stloc.Value, binary.Right, targetType, CompoundAssignmentType.EvaluatesToOldValue));
if (binary != null) {
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;
}
@ -521,19 +562,35 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -521,19 +562,35 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (!IsCompoundStore(store, out var targetType, out var value))
return false;
if (!IsMatchingCompoundLoad(inst.Value, store, inst.Variable))
if (IsImplicitTruncation(inst.Value, targetType)) {
// 'stloc l' is implicitly truncating the value
return false;
var binary = UnwrapSmallIntegerConv(value, out var conv) as BinaryNumericInstruction;
if (binary == null || !binary.Left.MatchLdLoc(inst.Variable) || !binary.Right.MatchLdcI4(1))
return false;
if (!(binary.Operator == BinaryNumericOperator.Add || binary.Operator == BinaryNumericOperator.Sub))
return false;
if (!ValidateCompoundAssign(binary, conv, targetType))
}
if (!IsMatchingCompoundLoad(inst.Value, store, inst.Variable))
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;
context.Step("TransformPostIncDecOperator", inst);
inst.Value = new CompoundAssignmentInstruction(binary, inst.Value, binary.Right, targetType, CompoundAssignmentType.EvaluatesToOldValue);
}
block.Instructions.RemoveAt(i + 1);
if (inst.Variable.IsSingleDefinition && inst.Variable.LoadCount == 0) {
// dead store -> it was a statement-level post-increment

Loading…
Cancel
Save