Browse Source

Readonly struct method calls on readonly lvalues don't generate temporaries.

pull/1471/head
Daniel Grunwald 6 years ago
parent
commit
416425b681
  1. 63
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs
  2. 83
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

63
ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs

@ -58,8 +58,44 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -58,8 +58,44 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
private static void Test(ref S byRef)
{
}
public void CallOnThis()
{
// distinguish calls on 'this' from calls on a copy of 'this'
SetField();
S s = this;
s.SetField();
}
}
#if CS72
public readonly struct R
{
public readonly int Field;
public int Property {
get {
return Field;
}
set {
Console.WriteLine("Setter on readonly struct");
}
}
public void Method()
{
}
public void CallOnThis()
{
// distinguish calls on 'this' from calls on a copy of 'this'
Method();
R r = this;
r.Method();
}
}
#endif
#if ROSLYN && OPT
// Roslyn optimizes out the explicit default-initialization
private static readonly S ReadOnlyS;
@ -69,6 +105,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -69,6 +105,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
private static S MutableS = default(S);
#endif
private static volatile int VolatileInt;
#if CS72
private static readonly R ReadOnlyR;
private static R MutableR;
#endif
public static void CallMethodViaField()
{
@ -76,6 +116,14 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -76,6 +116,14 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
MutableS.SetField();
S mutableS = MutableS;
mutableS.SetField();
#if CS72
ReadOnlyR.Method();
R readOnlyR = ReadOnlyR;
readOnlyR.Method();
R mutableR = MutableR;
mutableR.Method();
#endif
}
#if !(ROSLYN && OPT) || COPY_PROPAGATION_FIXED
@ -194,15 +242,26 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -194,15 +242,26 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
}
public static T Get<T>()
{
return default(T);
}
public static void CallOnTemporary()
{
// Method can be called directly on temporaries
//InitObj2().MethodCalls();
Get<S>().MethodCalls();
// Setting a property requires a temporary to avoid
// CS1612 Cannot modify the return value of 'InitObj2()' because it is not a variable
S s = InitObj2();
S s = Get<S>();
s.Property = 1;
#if CS72
Get<R>().Method();
R r = Get<R>();
r.Property = 2;
#endif
}
}
}

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

@ -264,7 +264,22 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -264,7 +264,22 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// Thus, we have to ensure we're operating on an r-value.
// Additionally, we cannot inline in cases where the C# compiler prohibits the direct use
// of the rvalue (e.g. M(ref (MyStruct)obj); is invalid).
return IsUsedAsThisPointerInCall(loadInst) && !IsLValue(inlinedExpression);
if (!IsUsedAsThisPointerInCall(loadInst))
return false;
switch (ClassifyExpression(inlinedExpression)) {
case ExpressionClassification.RValue:
// For struct method calls on rvalues, the C# compiler always generates temporaries.
return true;
case ExpressionClassification.MutableLValue:
// For struct method calls on mutable lvalues, the C# compiler never generates temporaries.
return false;
case ExpressionClassification.ReadonlyLValue:
// For struct method calls on readonly lvalues, the C# compiler
// only generates a temporary if it isn't a "readonly struct"
return !(v.Type.GetDefinition()?.IsReadOnly == true);
default:
throw new InvalidOperationException("invalid expression classification");
}
}
internal static bool IsUsedAsThisPointerInCall(LdLoca ldloca)
@ -289,37 +304,87 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -289,37 +304,87 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
internal enum ExpressionClassification
{
RValue,
MutableLValue,
ReadonlyLValue,
}
/// <summary>
/// Gets whether the instruction, when converted into C#, turns into an l-value that can
/// be used to mutate a value-type.
/// If this function returns false, the C# compiler would introduce a temporary copy
/// when calling a method on a value-type (and any mutations performed by the method will be lost)
/// </summary>
static bool IsLValue(ILInstruction inst)
internal static ExpressionClassification ClassifyExpression(ILInstruction inst)
{
switch (inst.OpCode) {
case OpCode.LdLoc:
case OpCode.StLoc:
return true;
if (IsReadonlyRefLocal(((IInstructionWithVariableOperand)inst).Variable)) {
return ExpressionClassification.ReadonlyLValue;
} else {
return ExpressionClassification.MutableLValue;
}
case OpCode.LdObj:
// ldobj typically refers to a storage location,
// but readonly fields are an exception.
IField f = (((LdObj)inst).Target as IInstructionWithFieldOperand)?.Field;
return !(f != null && f.IsReadOnly);
if (IsReadonlyReference(((LdObj)inst).Target)) {
return ExpressionClassification.ReadonlyLValue;
} else {
return ExpressionClassification.MutableLValue;
}
case OpCode.StObj:
// stobj is the same as ldobj.
f = (((StObj)inst).Target as IInstructionWithFieldOperand)?.Field;
return !(f != null && f.IsReadOnly);
if (IsReadonlyReference(((StObj)inst).Target)) {
return ExpressionClassification.ReadonlyLValue;
} else {
return ExpressionClassification.MutableLValue;
}
case OpCode.Call:
var m = ((CallInstruction)inst).Method;
// multi-dimensional array getters are lvalues,
// everything else is an rvalue.
return m.DeclaringType.Kind == TypeKind.Array;
if (m.DeclaringType.Kind == TypeKind.Array) {
return ExpressionClassification.MutableLValue;
} else {
return ExpressionClassification.RValue;
}
default:
return ExpressionClassification.RValue; // most instructions result in an rvalue
}
}
private static bool IsReadonlyReference(ILInstruction addr)
{
switch (addr) {
case LdFlda ldflda:
return ldflda.Field.IsReadOnly;
case LdsFlda ldsflda:
return ldsflda.Field.IsReadOnly;
case LdLoc ldloc:
return IsReadonlyRefLocal(ldloc.Variable);
case Call call:
// TODO: calls with 'readonly ref' return
default:
return false; // most instructions result in an rvalue
return false;
}
}
private static bool IsReadonlyRefLocal(ILVariable variable)
{
if (variable.Kind == VariableKind.Parameter) {
if (variable.Index == -1) {
// this parameter in readonly struct
return variable.Function.Method?.DeclaringTypeDefinition?.IsReadOnly == true;
} else {
return variable.Function.Parameters[variable.Index.Value].IsIn;
}
}
return false;
}
/// <summary>
/// Determines whether a variable should be inlined in non-aggressive mode, even though it is not a generated variable.
/// </summary>

Loading…
Cancel
Save