@ -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 ( HandleCallC ompoundAssign ( 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 ( ! CompoundAssignmentInstructio n . IsBinaryCompatibleWithType ( binary , targetType ) )
if ( ! Numeric CompoundAssign. 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