Browse Source

Redesign CompoundAssignmentInstruction to support an 'Address' mode where we don't implicitly turn an LdObj into an StObj, but instead directly operate on an address.

This means we can get rid of the special case in TransformDisplayClassUsage, as compound.assign can now also be used with the address of a local variable.
pull/1596/head
Daniel Grunwald 6 years ago
parent
commit
f2151972a0
  1. 65
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  2. 4
      ICSharpCode.Decompiler/IL/Instructions.cs
  3. 153
      ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs
  4. 3
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  5. 56
      ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs
  6. 10
      ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs

65
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -1326,7 +1326,13 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1326,7 +1326,13 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override TranslatedExpression VisitUserDefinedCompoundAssign(UserDefinedCompoundAssign inst, TranslationContext context)
{
var target = Translate(inst.Target);
IType loadType = inst.Method.Parameters[0].Type;
ExpressionWithResolveResult target;
if (inst.TargetKind == CompoundTargetKind.Address) {
target = LdObj(inst.Target, loadType);
} else {
target = Translate(inst.Target, loadType);
}
if (UserDefinedCompoundAssign.IsStringConcat(inst.Method)) {
Debug.Assert(inst.Method.Parameters.Count == 2);
var value = Translate(inst.Value).ConvertTo(inst.Method.Parameters[1].Type, this, allowImplicitConversion: true);
@ -1342,7 +1348,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1342,7 +1348,7 @@ namespace ICSharpCode.Decompiler.CSharp
.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);
UnaryOperatorType? op = GetUnaryOperatorTypeFromMetadataName(inst.Method.Name, inst.EvalMode == CompoundEvalMode.EvaluatesToOldValue);
Debug.Assert(op != null);
return new UnaryOperatorExpression(op.Value, target)
@ -1421,10 +1427,15 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1421,10 +1427,15 @@ namespace ICSharpCode.Decompiler.CSharp
TranslatedExpression HandleCompoundAssignment(NumericCompoundAssign inst, AssignmentOperatorType op)
{
var target = Translate(inst.Target);
ExpressionWithResolveResult target;
if (inst.TargetKind == CompoundTargetKind.Address) {
target = LdObj(inst.Target, inst.Type);
} else {
target = Translate(inst.Target, inst.Type);
}
TranslatedExpression resultExpr;
if (inst.CompoundAssignmentType == CompoundAssignmentType.EvaluatesToOldValue) {
if (inst.EvalMode == CompoundEvalMode.EvaluatesToOldValue) {
Debug.Assert(op == AssignmentOperatorType.Add || op == AssignmentOperatorType.Subtract);
Debug.Assert(inst.Value.MatchLdcI(1));
UnaryOperatorType unary;
@ -1485,8 +1496,13 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1485,8 +1496,13 @@ namespace ICSharpCode.Decompiler.CSharp
TranslatedExpression HandleCompoundShift(NumericCompoundAssign inst, AssignmentOperatorType op)
{
Debug.Assert(inst.CompoundAssignmentType == CompoundAssignmentType.EvaluatesToNewValue);
var target = Translate(inst.Target);
Debug.Assert(inst.EvalMode == CompoundEvalMode.EvaluatesToNewValue);
ExpressionWithResolveResult target;
if (inst.TargetKind == CompoundTargetKind.Address) {
target = LdObj(inst.Target, inst.Type);
} else {
target = Translate(inst.Target, inst.Type);
}
var value = Translate(inst.Value);
// Shift operators in C# always expect type 'int' on the right-hand-side
@ -1901,46 +1917,46 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1901,46 +1917,46 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override TranslatedExpression VisitLdObj(LdObj inst, TranslationContext context)
{
var target = Translate(inst.Target);
if (TypeUtils.IsCompatiblePointerTypeForMemoryAccess(target.Type, inst.Type)) {
TranslatedExpression result;
var result = LdObj(inst.Target, inst.Type);
//if (target.Type.IsSmallIntegerType() && loadType.IsSmallIntegerType() && target.Type.GetSign() != loadType.GetSign())
// return result.ConvertTo(loadType, this);
return result.WithILInstruction(inst);
}
ExpressionWithResolveResult LdObj(ILInstruction address, IType loadType)
{
var target = Translate(address);
if (TypeUtils.IsCompatiblePointerTypeForMemoryAccess(target.Type, loadType)) {
ExpressionWithResolveResult result;
if (target.Expression is DirectionExpression dirExpr) {
// we can dereference the managed reference by stripping away the 'ref'
result = target.UnwrapChild(dirExpr.Expression);
result.Expression.AddAnnotation(inst); // add LdObj in addition to the existing ILInstruction annotation
} else if (target.Type is PointerType pointerType) {
if (target.Expression is UnaryOperatorExpression uoe && uoe.Operator == UnaryOperatorType.AddressOf) {
// We can dereference the pointer by stripping away the '&'
result = target.UnwrapChild(uoe.Expression);
result.Expression.AddAnnotation(inst); // add LdObj in addition to the existing ILInstruction annotation
} else {
// Dereference the existing pointer
result = new UnaryOperatorExpression(UnaryOperatorType.Dereference, target.Expression)
.WithILInstruction(inst)
.WithRR(new ResolveResult(pointerType.ElementType));
}
} else {
// reference type behind non-DirectionExpression?
// this case should be impossible, but we can use a pointer cast
// just to make sure
target = target.ConvertTo(new PointerType(inst.Type), this);
target = target.ConvertTo(new PointerType(loadType), this);
return new UnaryOperatorExpression(UnaryOperatorType.Dereference, target.Expression)
.WithILInstruction(inst)
.WithRR(new ResolveResult(inst.Type));
.WithRR(new ResolveResult(loadType));
}
// we don't convert result to inst.Type, because the LdObj type
// might be inaccurate (it's often System.Object for all reference types),
// and our parent node should already insert casts where necessary
if (target.Type.IsSmallIntegerType() && inst.Type.IsSmallIntegerType() && target.Type.GetSign() != inst.Type.GetSign())
return result.ConvertTo(inst.Type, this);
return result;
} else {
// We need to cast the pointer type:
target = target.ConvertTo(new PointerType(inst.Type), this);
target = target.ConvertTo(new PointerType(loadType), this);
return new UnaryOperatorExpression(UnaryOperatorType.Dereference, target.Expression)
.WithILInstruction(inst)
.WithRR(new ResolveResult(inst.Type));
.WithRR(new ResolveResult(loadType));
}
}
@ -3039,7 +3055,12 @@ namespace ICSharpCode.Decompiler.CSharp @@ -3039,7 +3055,12 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override TranslatedExpression VisitDynamicCompoundAssign(DynamicCompoundAssign inst, TranslationContext context)
{
var target = TranslateDynamicArgument(inst.Target, inst.TargetArgumentInfo);
ExpressionWithResolveResult target;
if (inst.TargetKind == CompoundTargetKind.Address) {
target = LdObj(inst.Target, SpecialType.Dynamic);
} else {
target = TranslateDynamicArgument(inst.Target, inst.TargetArgumentInfo);
}
var value = TranslateDynamicArgument(inst.Value, inst.ValueArgumentInfo);
var ae = new AssignmentExpression(target, AssignmentExpression.GetAssignmentOperatorTypeFromExpressionType(inst.Operation).Value, value);

4
ICSharpCode.Decompiler/IL/Instructions.cs

@ -1092,7 +1092,7 @@ namespace ICSharpCode.Decompiler.IL @@ -1092,7 +1092,7 @@ namespace ICSharpCode.Decompiler.IL
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{
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);
return o != null && this.Method.Equals(o.Method) && this.EvalMode == o.EvalMode && Target.PerformMatch(o.Target, ref match) && Value.PerformMatch(o.Value, ref match);
}
}
}
@ -1126,7 +1126,7 @@ namespace ICSharpCode.Decompiler.IL @@ -1126,7 +1126,7 @@ namespace ICSharpCode.Decompiler.IL
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{
var o = other as DynamicCompoundAssign;
return o != null && this.CompoundAssignmentType == o.CompoundAssignmentType && Target.PerformMatch(o.Target, ref match) && Value.PerformMatch(o.Value, ref match);
return o != null && this.EvalMode == o.EvalMode && Target.PerformMatch(o.Target, ref match) && Value.PerformMatch(o.Value, ref match);
}
}
}

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

@ -23,36 +23,101 @@ using ICSharpCode.Decompiler.TypeSystem; @@ -23,36 +23,101 @@ using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.IL
{
public enum CompoundAssignmentType : byte
public enum CompoundEvalMode : byte
{
/// <summary>
/// The compound.assign instruction will evaluate to the old value.
/// This mode is used only for post-increment/decrement.
/// </summary>
EvaluatesToOldValue,
/// <summary>
/// The compound.assign instruction will evaluate to the new value.
/// This mode is used for compound assignments and pre-increment/decrement.
/// </summary>
EvaluatesToNewValue
}
public enum CompoundTargetKind : byte
{
/// <summary>
/// The target is an instruction computing an address,
/// and the compound.assign will implicitly load/store from/to that address.
/// </summary>
Address,
/// <summary>
/// The Target must be a call to a property getter,
/// and the compound.assign will implicitly call the corresponding property setter.
/// </summary>
Property,
/// <summary>
/// The target is a dynamic call.
/// </summary>
Dynamic
}
public abstract partial class CompoundAssignmentInstruction : ILInstruction
{
public readonly CompoundAssignmentType CompoundAssignmentType;
public readonly CompoundEvalMode EvalMode;
/// <summary>
/// If TargetIsProperty is true, the Target must be a call to a property getter,
/// and the compound.assign will implicitly call the corresponding property setter.
/// Otherwise, the Target can be any instruction that evaluates to an address,
/// and the compound.assign will implicit load and store from/to that address.
/// </summary>
public readonly CompoundTargetKind TargetKind;
public CompoundAssignmentInstruction(OpCode opCode, CompoundAssignmentType compoundAssignmentType, ILInstruction target, ILInstruction value)
public CompoundAssignmentInstruction(OpCode opCode, CompoundEvalMode evalMode, ILInstruction target, CompoundTargetKind targetKind, ILInstruction value)
: base(opCode)
{
this.CompoundAssignmentType = compoundAssignmentType;
this.EvalMode = evalMode;
this.Target = target;
this.TargetKind = targetKind;
this.Value = value;
CheckValidTarget();
}
internal static bool IsValidCompoundAssignmentTarget(ILInstruction inst)
internal override void CheckInvariant(ILPhase phase)
{
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;
base.CheckInvariant(phase);
CheckValidTarget();
}
[Conditional("DEBUG")]
void CheckValidTarget()
{
switch (TargetKind) {
case CompoundTargetKind.Address:
Debug.Assert(target.ResultType == StackType.Ref || target.ResultType == StackType.I);
break;
case CompoundTargetKind.Property:
Debug.Assert(target.OpCode == OpCode.Call || target.OpCode == OpCode.CallVirt);
var owner = ((CallInstruction)target).Method.AccessorOwner as IProperty;
Debug.Assert(owner != null && owner.CanSet);
break;
case CompoundTargetKind.Dynamic:
Debug.Assert(target.OpCode == OpCode.DynamicGetMemberInstruction || target.OpCode == OpCode.DynamicGetIndexInstruction);
break;
}
}
protected void WriteSuffix(ITextOutput output)
{
switch (TargetKind) {
case CompoundTargetKind.Address:
output.Write(".address");
break;
case CompoundTargetKind.Property:
output.Write(".property");
break;
}
switch (EvalMode) {
case CompoundEvalMode.EvaluatesToNewValue:
output.Write(".new");
break;
case CompoundEvalMode.EvaluatesToOldValue:
output.Write(".old");
break;
}
}
}
@ -82,8 +147,9 @@ namespace ICSharpCode.Decompiler.IL @@ -82,8 +147,9 @@ namespace ICSharpCode.Decompiler.IL
public bool IsLifted { get; }
public NumericCompoundAssign(BinaryNumericInstruction binary, ILInstruction target, ILInstruction value, IType type, CompoundAssignmentType compoundAssignmentType)
: base(OpCode.NumericCompoundAssign, compoundAssignmentType, target, value)
public NumericCompoundAssign(BinaryNumericInstruction binary, ILInstruction target,
CompoundTargetKind targetKind, ILInstruction value, IType type, CompoundEvalMode evalMode)
: base(OpCode.NumericCompoundAssign, evalMode, target, targetKind, value)
{
Debug.Assert(IsBinaryCompatibleWithType(binary, type));
this.CheckForOverflow = binary.CheckForOverflow;
@ -95,8 +161,7 @@ namespace ICSharpCode.Decompiler.IL @@ -95,8 +161,7 @@ namespace ICSharpCode.Decompiler.IL
this.IsLifted = binary.IsLifted;
this.type = type;
this.AddILRange(binary);
Debug.Assert(compoundAssignmentType == CompoundAssignmentType.EvaluatesToNewValue || (Operator == BinaryNumericOperator.Add || Operator == BinaryNumericOperator.Sub));
Debug.Assert(IsValidCompoundAssignmentTarget(Target));
Debug.Assert(evalMode == CompoundEvalMode.EvaluatesToNewValue || (Operator == BinaryNumericOperator.Add || Operator == BinaryNumericOperator.Sub));
Debug.Assert(this.ResultType == (IsLifted ? StackType.O : UnderlyingResultType));
}
@ -176,16 +241,20 @@ namespace ICSharpCode.Decompiler.IL @@ -176,16 +241,20 @@ namespace ICSharpCode.Decompiler.IL
WriteILRange(output, options);
output.Write(OpCode);
output.Write("." + BinaryNumericInstruction.GetOperatorName(Operator));
if (CompoundAssignmentType == CompoundAssignmentType.EvaluatesToNewValue)
output.Write(".new");
else
output.Write(".old");
if (CheckForOverflow)
if (CheckForOverflow) {
output.Write(".ovf");
if (Sign == Sign.Unsigned)
}
if (Sign == Sign.Unsigned) {
output.Write(".unsigned");
else if (Sign == Sign.Signed)
} else if (Sign == Sign.Signed) {
output.Write(".signed");
}
output.Write('.');
output.Write(UnderlyingResultType.ToString().ToLowerInvariant());
if (IsLifted) {
output.Write(".lifted");
}
base.WriteSuffix(output);
output.Write('(');
Target.WriteTo(output, options);
output.Write(", ");
@ -199,13 +268,13 @@ namespace ICSharpCode.Decompiler.IL @@ -199,13 +268,13 @@ namespace ICSharpCode.Decompiler.IL
public readonly IMethod Method;
public bool IsLifted => false; // TODO: implement lifted user-defined compound assignments
public UserDefinedCompoundAssign(IMethod method, CompoundAssignmentType compoundAssignmentType, ILInstruction target, ILInstruction value)
: base(OpCode.UserDefinedCompoundAssign, compoundAssignmentType, target, value)
public UserDefinedCompoundAssign(IMethod method, CompoundEvalMode evalMode,
ILInstruction target, CompoundTargetKind targetKind, ILInstruction value)
: base(OpCode.UserDefinedCompoundAssign, evalMode, target, targetKind, value)
{
this.Method = method;
Debug.Assert(Method.IsOperator || IsStringConcat(method));
Debug.Assert(compoundAssignmentType == CompoundAssignmentType.EvaluatesToNewValue || (Method.Name == "op_Increment" || Method.Name == "op_Decrement"));
Debug.Assert(IsValidCompoundAssignmentTarget(Target));
Debug.Assert(evalMode == CompoundEvalMode.EvaluatesToNewValue || (Method.Name == "op_Increment" || Method.Name == "op_Decrement"));
}
public static bool IsStringConcat(IMethod method)
@ -219,11 +288,7 @@ namespace ICSharpCode.Decompiler.IL @@ -219,11 +288,7 @@ namespace ICSharpCode.Decompiler.IL
{
WriteILRange(output, options);
output.Write(OpCode);
if (CompoundAssignmentType == CompoundAssignmentType.EvaluatesToNewValue)
output.Write(".new");
else
output.Write(".old");
base.WriteSuffix(output);
output.Write(' ');
Method.WriteTo(output);
output.Write('(');
@ -241,8 +306,11 @@ namespace ICSharpCode.Decompiler.IL @@ -241,8 +306,11 @@ namespace ICSharpCode.Decompiler.IL
public CSharpArgumentInfo ValueArgumentInfo { get; }
public CSharpBinderFlags BinderFlags { get; }
public DynamicCompoundAssign(ExpressionType op, CSharpBinderFlags binderFlags, ILInstruction target, CSharpArgumentInfo targetArgumentInfo, ILInstruction value, CSharpArgumentInfo valueArgumentInfo)
: base(OpCode.DynamicCompoundAssign, CompoundAssignmentTypeFromOperation(op), target, value)
public DynamicCompoundAssign(ExpressionType op, CSharpBinderFlags binderFlags,
ILInstruction target, CSharpArgumentInfo targetArgumentInfo,
ILInstruction value, CSharpArgumentInfo valueArgumentInfo,
CompoundTargetKind targetKind = CompoundTargetKind.Dynamic)
: base(OpCode.DynamicCompoundAssign, CompoundEvalModeFromOperation(op), target, targetKind, value)
{
if (!IsExpressionTypeSupported(op))
throw new ArgumentOutOfRangeException("op");
@ -258,10 +326,7 @@ namespace ICSharpCode.Decompiler.IL @@ -258,10 +326,7 @@ namespace ICSharpCode.Decompiler.IL
output.Write(OpCode);
output.Write("." + Operation.ToString().ToLower());
DynamicInstruction.WriteBinderFlags(BinderFlags, output, options);
if (CompoundAssignmentType == CompoundAssignmentType.EvaluatesToNewValue)
output.Write(".new");
else
output.Write(".old");
base.WriteSuffix(output);
output.Write(' ');
DynamicInstruction.WriteArgumentList(output, options, (Target, TargetArgumentInfo), (Value, ValueArgumentInfo));
}
@ -287,14 +352,14 @@ namespace ICSharpCode.Decompiler.IL @@ -287,14 +352,14 @@ namespace ICSharpCode.Decompiler.IL
|| type == ExpressionType.SubtractAssignChecked;
}
static CompoundAssignmentType CompoundAssignmentTypeFromOperation(ExpressionType op)
static CompoundEvalMode CompoundEvalModeFromOperation(ExpressionType op)
{
switch (op) {
case ExpressionType.PostIncrementAssign:
case ExpressionType.PostDecrementAssign:
return CompoundAssignmentType.EvaluatesToOldValue;
return CompoundEvalMode.EvaluatesToOldValue;
default:
return CompoundAssignmentType.EvaluatesToNewValue;
return CompoundEvalMode.EvaluatesToNewValue;
}
}
}

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

@ -424,10 +424,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -424,10 +424,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; // inline into (left slot of) user-defined && or || operator
case OpCode.DynamicGetMemberInstruction:
case OpCode.DynamicGetIndexInstruction:
case OpCode.LdObj:
if (parent.Parent.OpCode == OpCode.DynamicCompoundAssign)
return true; // inline into dynamic compound assignments
break;
case OpCode.DynamicCompoundAssign:
return true;
case OpCode.ArrayToPointer:
case OpCode.LocAllocSpan:
return true; // inline size-expressions into localloc.span

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

@ -276,18 +276,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -276,18 +276,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
ILInstruction newInst;
if (UnwrapSmallIntegerConv(setterValue, out var smallIntConv) is BinaryNumericInstruction binary) {
if (!IsMatchingCompoundLoad(binary.Left, compoundStore, forbiddenVariable: storeInSetter?.Variable))
if (!IsMatchingCompoundLoad(binary.Left, compoundStore, out var target, out var targetKind, forbiddenVariable: storeInSetter?.Variable))
return false;
if (!ValidateCompoundAssign(binary, smallIntConv, targetType))
return false;
context.Step($"Compound assignment (binary.numeric)", compoundStore);
newInst = new NumericCompoundAssign(
binary, binary.Left, binary.Right,
targetType, CompoundAssignmentType.EvaluatesToNewValue);
binary, target, targetKind, binary.Right,
targetType, CompoundEvalMode.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))
if (!IsMatchingCompoundLoad(operatorCall.Arguments[0], compoundStore, out var target, out var targetKind, forbiddenVariable: storeInSetter?.Variable))
return false;
ILInstruction rhs;
if (operatorCall.Arguments.Count == 2) {
@ -305,24 +305,24 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -305,24 +305,24 @@ namespace ICSharpCode.Decompiler.IL.Transforms
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);
newInst = new UserDefinedCompoundAssign(operatorCall.Method, CompoundEvalMode.EvaluatesToNewValue,
target, targetKind, rhs);
} else if (setterValue is DynamicBinaryOperatorInstruction dynamicBinaryOp) {
if (!IsMatchingCompoundLoad(dynamicBinaryOp.Left, compoundStore, forbiddenVariable: storeInSetter?.Variable))
if (!IsMatchingCompoundLoad(dynamicBinaryOp.Left, compoundStore, out var target, out var targetKind, forbiddenVariable: storeInSetter?.Variable))
return false;
context.Step($"Compound assignment (dynamic binary)", compoundStore);
newInst = new DynamicCompoundAssign(dynamicBinaryOp.Operation, dynamicBinaryOp.BinderFlags, dynamicBinaryOp.Left, dynamicBinaryOp.LeftArgumentInfo, dynamicBinaryOp.Right, dynamicBinaryOp.RightArgumentInfo);
newInst = new DynamicCompoundAssign(dynamicBinaryOp.Operation, dynamicBinaryOp.BinderFlags, target, dynamicBinaryOp.LeftArgumentInfo, dynamicBinaryOp.Right, dynamicBinaryOp.RightArgumentInfo, targetKind);
} else if (setterValue is Call concatCall && UserDefinedCompoundAssign.IsStringConcat(concatCall.Method)) {
// setterValue is a string.Concat() invocation
if (concatCall.Arguments.Count != 2)
return false; // for now we only support binary compound assignments
if (!targetType.IsKnownType(KnownTypeCode.String))
return false;
if (!IsMatchingCompoundLoad(concatCall.Arguments[0], compoundStore, forbiddenVariable: storeInSetter?.Variable))
if (!IsMatchingCompoundLoad(concatCall.Arguments[0], compoundStore, out var target, out var targetKind, forbiddenVariable: storeInSetter?.Variable))
return false;
context.Step($"Compound assignment (string concatenation)", compoundStore);
newInst = new UserDefinedCompoundAssign(concatCall.Method, CompoundAssignmentType.EvaluatesToNewValue,
concatCall.Arguments[0], concatCall.Arguments[1]);
newInst = new UserDefinedCompoundAssign(concatCall.Method, CompoundEvalMode.EvaluatesToNewValue,
target, targetKind, concatCall.Arguments[1]);
} else {
return false;
}
@ -469,7 +469,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -469,7 +469,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// <summary>
/// Gets whether 'inst' is a possible store for use as a compound store.
/// </summary>
static bool IsCompoundStore(ILInstruction inst, out IType storeType, out ILInstruction value, ICompilation compilation)
/// <remarks>
/// Output parameters:
/// storeType: The type of the value being stored.
/// value: The value being stored (will be analyzed further to detect compound assignments)
///
/// Every IsCompoundStore() call should be followed by an IsMatchingCompoundLoad() call.
/// </remarks>
static bool IsCompoundStore(ILInstruction inst, out IType storeType,
out ILInstruction value, ICompilation compilation)
{
value = null;
storeType = null;
@ -511,18 +519,26 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -511,18 +519,26 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
static bool IsMatchingCompoundLoad(ILInstruction load, ILInstruction store, ILVariable forbiddenVariable)
static bool IsMatchingCompoundLoad(ILInstruction load, ILInstruction store,
out ILInstruction target, out CompoundTargetKind targetKind,
ILVariable forbiddenVariable = null)
{
target = null;
targetKind = 0;
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 != null && forbiddenVariable.IsUsedWithin(ldobj.Target))
return false;
target = ldobj.Target;
targetKind = CompoundTargetKind.Address;
return ldobj.Target.Match(stobj.Target).Success;
} else if (MatchingGetterAndSetterCalls(load as CallInstruction, store as CallInstruction)) {
if (forbiddenVariable != null && forbiddenVariable.IsUsedWithin(load))
return false;
target = load;
targetKind = CompoundTargetKind.Property;
return true;
} else {
return false;
@ -574,18 +590,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -574,18 +590,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (!(stloc.Variable.Kind == VariableKind.Local || stloc.Variable.Kind == VariableKind.StackSlot))
return false;
if (!IsMatchingCompoundLoad(stloc.Value, store, stloc.Variable))
if (!IsMatchingCompoundLoad(stloc.Value, store, out var target, out var targetKind, forbiddenVariable: stloc.Variable))
return false;
if (IsImplicitTruncation(stloc.Value, stloc.Variable.Type, context.TypeSystem))
return false;
context.Step("TransformPostIncDecOperatorWithInlineStore", store);
if (binary != null) {
block.Instructions[pos] = new StLoc(stloc.Variable, new NumericCompoundAssign(
binary, stloc.Value, binary.Right, targetType, CompoundAssignmentType.EvaluatesToOldValue));
binary, target, targetKind, binary.Right, targetType, CompoundEvalMode.EvaluatesToOldValue));
} else {
Call operatorCall = (Call)value;
block.Instructions[pos] = new StLoc(stloc.Variable, new UserDefinedCompoundAssign(
operatorCall.Method, CompoundAssignmentType.EvaluatesToOldValue, stloc.Value, new LdcI4(1)));
operatorCall.Method, CompoundEvalMode.EvaluatesToOldValue, target, targetKind, new LdcI4(1)));
}
return true;
}
@ -612,7 +628,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -612,7 +628,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// 'stloc l' is implicitly truncating the value
return false;
}
if (!IsMatchingCompoundLoad(inst.Value, store, inst.Variable))
if (!IsMatchingCompoundLoad(inst.Value, store, out var target, out var targetKind, forbiddenVariable: inst.Variable))
return false;
if (UnwrapSmallIntegerConv(value, out var conv) is BinaryNumericInstruction binary) {
if (!binary.Left.MatchLdLoc(inst.Variable) || !binary.Right.MatchLdcI(1))
@ -622,8 +638,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -622,8 +638,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!ValidateCompoundAssign(binary, conv, targetType))
return false;
context.Step("TransformPostIncDecOperator (builtin)", inst);
inst.Value = new NumericCompoundAssign(binary, inst.Value, binary.Right,
targetType, CompoundAssignmentType.EvaluatesToOldValue);
inst.Value = new NumericCompoundAssign(binary, target, targetKind, binary.Right,
targetType, CompoundEvalMode.EvaluatesToOldValue);
} else if (value is Call operatorCall && operatorCall.Method.IsOperator && operatorCall.Arguments.Count == 1) {
if (!operatorCall.Arguments[0].MatchLdLoc(inst.Variable))
return false;
@ -633,7 +649,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -633,7 +649,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
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));
CompoundEvalMode.EvaluatesToOldValue, target, targetKind, new LdcI4(1));
} else {
return false;
}

10
ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs

@ -305,15 +305,5 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -305,15 +305,5 @@ namespace ICSharpCode.Decompiler.IL.Transforms
Debug.Fail("LdFlda pattern not supported!");
}
}
protected internal override void VisitNumericCompoundAssign(NumericCompoundAssign inst)
{
base.VisitNumericCompoundAssign(inst);
// NumericCompoundAssign is only valid when used with fields: -> replace it with a BinaryNumericInstruction.
if (inst.Target.MatchLdLoc(out var v)) {
inst.ReplaceWith(new StLoc(v, new BinaryNumericInstruction(inst.Operator, inst.Target, inst.Value, inst.CheckForOverflow, inst.Sign).WithILRange(inst)));
}
}
}
}

Loading…
Cancel
Save