|
|
|
@ -174,10 +174,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
@@ -174,10 +174,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
|
|
|
|
// because the ExpressionTransforms don't look into inline blocks, manually trigger HandleCallCompoundAssign
|
|
|
|
|
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); |
|
|
|
|
inlineBlock.ReplaceWith(compoundAssign); |
|
|
|
|
} |
|
|
|
|
Debug.Assert(!inlineBlock.IsConnected); |
|
|
|
|
} |
|
|
|
|
return true; |
|
|
|
|
} else { |
|
|
|
@ -205,8 +202,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
@@ -205,8 +202,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static bool MatchingGetterAndSetterCalls(CallInstruction getterCall, CallInstruction setterCall) |
|
|
|
|
static bool MatchingGetterAndSetterCalls(CallInstruction getterCall, CallInstruction setterCall, out Action<ILTransformContext> finalizeMatch) |
|
|
|
|
{ |
|
|
|
|
finalizeMatch = null; |
|
|
|
|
if (getterCall == null || setterCall == null || !IsSameMember(getterCall.Method.AccessorOwner, setterCall.Method.AccessorOwner)) |
|
|
|
|
return false; |
|
|
|
|
if (setterCall.OpCode != getterCall.OpCode) |
|
|
|
@ -218,12 +216,33 @@ namespace ICSharpCode.Decompiler.IL.Transforms
@@ -218,12 +216,33 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
|
|
|
|
return false; |
|
|
|
|
// Ensure that same arguments are passed to getterCall and setterCall:
|
|
|
|
|
for (int j = 0; j < getterCall.Arguments.Count; j++) { |
|
|
|
|
if (setterCall.Arguments[j].MatchStLoc(out var v) && v.IsSingleDefinition && v.LoadCount == 1) { |
|
|
|
|
if (getterCall.Arguments[j].MatchLdLoc(v)) { |
|
|
|
|
// OK, setter call argument is saved in temporary that is re-used for getter call
|
|
|
|
|
if (finalizeMatch == null) { |
|
|
|
|
finalizeMatch = AdjustArguments; |
|
|
|
|
} |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (!SemanticHelper.IsPure(getterCall.Arguments[j].Flags)) |
|
|
|
|
return false; |
|
|
|
|
if (!getterCall.Arguments[j].Match(setterCall.Arguments[j]).Success) |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
return true; |
|
|
|
|
|
|
|
|
|
void AdjustArguments(ILTransformContext context) |
|
|
|
|
{ |
|
|
|
|
Debug.Assert(setterCall.Arguments.Count == getterCall.Arguments.Count + 1); |
|
|
|
|
for (int j = 0; j < getterCall.Arguments.Count; j++) { |
|
|
|
|
if (setterCall.Arguments[j].MatchStLoc(out var v, out var value)) { |
|
|
|
|
Debug.Assert(v.IsSingleDefinition && v.LoadCount == 1); |
|
|
|
|
Debug.Assert(getterCall.Arguments[j].MatchLdLoc(v)); |
|
|
|
|
getterCall.Arguments[j] = value; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -275,18 +294,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms
@@ -275,18 +294,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
|
|
|
|
} |
|
|
|
|
ILInstruction newInst; |
|
|
|
|
if (UnwrapSmallIntegerConv(setterValue, out var smallIntConv) is BinaryNumericInstruction binary) { |
|
|
|
|
if (!IsMatchingCompoundLoad(binary.Left, compoundStore, out var target, out var targetKind, forbiddenVariable: storeInSetter?.Variable)) |
|
|
|
|
if (!IsMatchingCompoundLoad(binary.Left, compoundStore, out var target, out var targetKind, out var finalizeMatch, forbiddenVariable: storeInSetter?.Variable)) |
|
|
|
|
return false; |
|
|
|
|
if (!ValidateCompoundAssign(binary, smallIntConv, targetType)) |
|
|
|
|
return false; |
|
|
|
|
context.Step($"Compound assignment (binary.numeric)", compoundStore); |
|
|
|
|
finalizeMatch?.Invoke(context); |
|
|
|
|
newInst = new NumericCompoundAssign( |
|
|
|
|
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, out var target, out var targetKind, forbiddenVariable: storeInSetter?.Variable)) |
|
|
|
|
if (!IsMatchingCompoundLoad(operatorCall.Arguments[0], compoundStore, out var target, out var targetKind, out var finalizeMatch, forbiddenVariable: storeInSetter?.Variable)) |
|
|
|
|
return false; |
|
|
|
|
ILInstruction rhs; |
|
|
|
|
if (operatorCall.Arguments.Count == 2) { |
|
|
|
@ -304,12 +324,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
@@ -304,12 +324,14 @@ 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); |
|
|
|
|
finalizeMatch?.Invoke(context); |
|
|
|
|
newInst = new UserDefinedCompoundAssign(operatorCall.Method, CompoundEvalMode.EvaluatesToNewValue, |
|
|
|
|
target, targetKind, rhs); |
|
|
|
|
} else if (setterValue is DynamicBinaryOperatorInstruction dynamicBinaryOp) { |
|
|
|
|
if (!IsMatchingCompoundLoad(dynamicBinaryOp.Left, compoundStore, out var target, out var targetKind, forbiddenVariable: storeInSetter?.Variable)) |
|
|
|
|
if (!IsMatchingCompoundLoad(dynamicBinaryOp.Left, compoundStore, out var target, out var targetKind, out var finalizeMatch, forbiddenVariable: storeInSetter?.Variable)) |
|
|
|
|
return false; |
|
|
|
|
context.Step($"Compound assignment (dynamic binary)", compoundStore); |
|
|
|
|
finalizeMatch?.Invoke(context); |
|
|
|
|
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
|
|
|
|
@ -317,9 +339,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
@@ -317,9 +339,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
|
|
|
|
return false; // for now we only support binary compound assignments
|
|
|
|
|
if (!targetType.IsKnownType(KnownTypeCode.String)) |
|
|
|
|
return false; |
|
|
|
|
if (!IsMatchingCompoundLoad(concatCall.Arguments[0], compoundStore, out var target, out var targetKind, forbiddenVariable: storeInSetter?.Variable)) |
|
|
|
|
if (!IsMatchingCompoundLoad(concatCall.Arguments[0], compoundStore, out var target, out var targetKind, out var finalizeMatch, forbiddenVariable: storeInSetter?.Variable)) |
|
|
|
|
return false; |
|
|
|
|
context.Step($"Compound assignment (string concatenation)", compoundStore); |
|
|
|
|
finalizeMatch?.Invoke(context); |
|
|
|
|
newInst = new UserDefinedCompoundAssign(concatCall.Method, CompoundEvalMode.EvaluatesToNewValue, |
|
|
|
|
target, targetKind, concatCall.Arguments[1]); |
|
|
|
|
} else { |
|
|
|
@ -332,6 +355,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
@@ -332,6 +355,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
|
|
|
|
context.RequestRerun(); // moving stloc to top-level might trigger inlining
|
|
|
|
|
} |
|
|
|
|
compoundStore.ReplaceWith(newInst); |
|
|
|
|
if (newInst.Parent is Block inlineAssignBlock && inlineAssignBlock.Kind == BlockKind.CallInlineAssign) { |
|
|
|
|
// It's possible that we first replaced the instruction in an inline-assign helper block.
|
|
|
|
|
// In such a situation, we know from the block invariant that we're have a storeInSetter.
|
|
|
|
|
Debug.Assert(storeInSetter != null); |
|
|
|
|
Debug.Assert(storeInSetter.Variable.IsSingleDefinition && storeInSetter.Variable.LoadCount == 1); |
|
|
|
|
Debug.Assert(inlineAssignBlock.Instructions.Single() == storeInSetter); |
|
|
|
|
Debug.Assert(inlineAssignBlock.FinalInstruction.MatchLdLoc(storeInSetter.Variable)); |
|
|
|
|
// Block CallInlineAssign { stloc I_0(compound.op(...)); final: ldloc I_0 }
|
|
|
|
|
// --> compound.op(...)
|
|
|
|
|
inlineAssignBlock.ReplaceWith(storeInSetter.Value); |
|
|
|
|
} |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -468,6 +502,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
@@ -468,6 +502,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
foreach (var arg in call.Arguments.SkipLast(1)) { |
|
|
|
|
if (arg.MatchStLoc(out var v) && v.IsSingleDefinition && v.LoadCount == 1) { |
|
|
|
|
continue; // OK, IsMatchingCompoundLoad can perform an adjustment in this special case
|
|
|
|
|
} |
|
|
|
|
if (!SemanticHelper.IsPure(arg.Flags)) { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
@ -484,13 +521,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms
@@ -484,13 +521,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Checks whether 'load' and 'store' both access the same store, and can be combined to a compound assignment.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="load">The load instruction to test.</param>
|
|
|
|
|
/// <param name="store">The compound store to test against. Must have previously been tested via IsCompoundStore()</param>
|
|
|
|
|
/// <param name="target">The target to use for the compound assignment instruction.</param>
|
|
|
|
|
/// <param name="targetKind">The target kind to use for the compound assignment instruction.</param>
|
|
|
|
|
/// <param name="finalizeMatch">If set to a non-null value, call this delegate to fix up minor mismatches between getter and setter.</param>
|
|
|
|
|
/// <param name="forbiddenVariable">
|
|
|
|
|
/// If given a non-null value, this function returns false if the forbiddenVariable is used in the load/store instructions.
|
|
|
|
|
/// Some transforms effectively move a store around,
|
|
|
|
|
/// which is only valid if the variable stored to does not occur in the compound load/store.
|
|
|
|
|
/// </param>
|
|
|
|
|
static bool IsMatchingCompoundLoad(ILInstruction load, ILInstruction store, |
|
|
|
|
out ILInstruction target, out CompoundTargetKind targetKind, |
|
|
|
|
ILFunction contextFunction = null, |
|
|
|
|
out Action<ILTransformContext> finalizeMatch, |
|
|
|
|
ILVariable forbiddenVariable = null) |
|
|
|
|
{ |
|
|
|
|
target = null; |
|
|
|
|
targetKind = 0; |
|
|
|
|
finalizeMatch = null; |
|
|
|
|
if (load is LdObj ldobj && store is StObj stobj) { |
|
|
|
|
Debug.Assert(SemanticHelper.IsPure(stobj.Target.Flags)); |
|
|
|
|
if (!SemanticHelper.IsPure(ldobj.Target.Flags)) |
|
|
|
@ -500,7 +551,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
@@ -500,7 +551,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
|
|
|
|
target = ldobj.Target; |
|
|
|
|
targetKind = CompoundTargetKind.Address; |
|
|
|
|
return ldobj.Target.Match(stobj.Target).Success; |
|
|
|
|
} else if (MatchingGetterAndSetterCalls(load as CallInstruction, store as CallInstruction)) { |
|
|
|
|
} else if (MatchingGetterAndSetterCalls(load as CallInstruction, store as CallInstruction, out finalizeMatch)) { |
|
|
|
|
if (forbiddenVariable != null && forbiddenVariable.IsUsedWithin(load)) |
|
|
|
|
return false; |
|
|
|
|
target = load; |
|
|
|
@ -509,11 +560,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
@@ -509,11 +560,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
|
|
|
|
} else if (load is LdLoc ldloc && store is StLoc stloc && ILVariableEqualityComparer.Instance.Equals(ldloc.Variable, stloc.Variable)) { |
|
|
|
|
if (ILVariableEqualityComparer.Instance.Equals(ldloc.Variable, forbiddenVariable)) |
|
|
|
|
return false; |
|
|
|
|
if (contextFunction == null) |
|
|
|
|
return false; // locals only supported for the callers that specify the context
|
|
|
|
|
target = new LdLoca(ldloc.Variable).WithILRange(ldloc); |
|
|
|
|
targetKind = CompoundTargetKind.Address; |
|
|
|
|
contextFunction.RecombineVariables(ldloc.Variable, stloc.Variable); |
|
|
|
|
finalizeMatch = context => context.Function.RecombineVariables(ldloc.Variable, stloc.Variable); |
|
|
|
|
return true; |
|
|
|
|
} else { |
|
|
|
|
return false; |
|
|
|
@ -566,11 +615,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
@@ -566,11 +615,12 @@ 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, out var target, out var targetKind, forbiddenVariable: stloc.Variable)) |
|
|
|
|
if (!IsMatchingCompoundLoad(stloc.Value, store, out var target, out var targetKind, out var finalizeMatch, forbiddenVariable: stloc.Variable)) |
|
|
|
|
return false; |
|
|
|
|
if (IsImplicitTruncation(stloc.Value, stloc.Variable.Type, context.TypeSystem)) |
|
|
|
|
return false; |
|
|
|
|
context.Step("TransformPostIncDecOperatorWithInlineStore", store); |
|
|
|
|
finalizeMatch?.Invoke(context); |
|
|
|
|
if (binary != null) { |
|
|
|
|
block.Instructions[pos] = new StLoc(stloc.Variable, new NumericCompoundAssign( |
|
|
|
|
binary, target, targetKind, binary.Right, targetType, CompoundEvalMode.EvaluatesToOldValue)); |
|
|
|
@ -614,7 +664,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
@@ -614,7 +664,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
|
|
|
|
// 'stloc tmp' is implicitly truncating the value
|
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
if (!IsMatchingCompoundLoad(inst.Value, store, out var target, out var targetKind, context.Function, forbiddenVariable: inst.Variable)) |
|
|
|
|
if (!IsMatchingCompoundLoad(inst.Value, store, out var target, out var targetKind, out var finalizeMatch, forbiddenVariable: inst.Variable)) |
|
|
|
|
return false; |
|
|
|
|
if (UnwrapSmallIntegerConv(value, out var conv) is BinaryNumericInstruction binary) { |
|
|
|
|
if (!binary.Left.MatchLdLoc(tmpVar) || !binary.Right.MatchLdcI(1)) |
|
|
|
@ -624,6 +674,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
@@ -624,6 +674,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
|
|
|
|
if (!ValidateCompoundAssign(binary, conv, targetType)) |
|
|
|
|
return false; |
|
|
|
|
context.Step("TransformPostIncDecOperator (builtin)", inst); |
|
|
|
|
finalizeMatch?.Invoke(context); |
|
|
|
|
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) { |
|
|
|
@ -634,6 +685,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
@@ -634,6 +685,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
|
|
|
|
if (operatorCall.IsLifted) |
|
|
|
|
return false; // TODO: add tests and think about whether nullables need special considerations
|
|
|
|
|
context.Step("TransformPostIncDecOperator (user-defined)", inst); |
|
|
|
|
finalizeMatch?.Invoke(context); |
|
|
|
|
inst.Value = new UserDefinedCompoundAssign(operatorCall.Method, |
|
|
|
|
CompoundEvalMode.EvaluatesToOldValue, target, targetKind, new LdcI4(1)); |
|
|
|
|
} else { |
|
|
|
|