Skip to content

Commit

Permalink
Emit defensive copy for constrained call on in parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
jcouv committed Jan 3, 2023
1 parent bc0716a commit 0085a72
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 2 deletions.
4 changes: 2 additions & 2 deletions src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1626,15 +1626,15 @@ private void EmitInstanceCallExpression(BoundCall call, UseKind useKind)
}
else
{
// calling a method defined in a base class.
// calling a method defined in a base class or interface.

// When calling a method that is virtual in metadata on a struct receiver,
// we use a constrained virtual call. If possible, it will skip boxing.
if (method.IsMetadataVirtual())
{
// NB: all methods that a struct could inherit from bases are non-mutating
// treat receiver as ReadOnly
tempOpt = EmitReceiverRef(receiver, AddressKind.ReadOnly);
tempOpt = EmitReceiverRef(receiver, methodContainingType.IsInterface ? AddressKind.Writeable : AddressKind.ReadOnly);
callKind = CallKind.ConstrainedCallVirt;
}
else
Expand Down
142 changes: 142 additions & 0 deletions src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenReadonlyStructTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2588,5 +2588,147 @@ void validate(ModuleSymbol module)
AssertDeclaresType(peModule, WellKnownType.System_Runtime_CompilerServices_IsReadOnlyAttribute, Accessibility.Internal);
}
}

[Fact, WorkItem(66135, "https://github.com/dotnet/roslyn/issues/66135")]
public void ConstrainedCallOnInParameter()
{
var source = @"
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public class C
{
public static void Main()
{
S value = new();
ref readonly S valueRef = ref value;
Console.Write(valueRef);
M(in valueRef);
Console.Write(valueRef);
}
public static void M(in S value)
{
foreach (var x in value) { }
}
}
public struct S : IEnumerable<int>
{
int a;
public readonly override string ToString() => a.ToString();
private IEnumerator<int> GetEnumerator() => Enumerable.Range(0, ++a).GetEnumerator();
IEnumerator<int> IEnumerable<int>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}";
var verifier = CompileAndVerify(source, expectedOutput: "00");
verifier.VerifyIL("C.M", """
{
// Code size 51 (0x33)
.maxstack 1
.locals init (System.Collections.Generic.IEnumerator<int> V_0,
S V_1)
IL_0000: ldarg.0
IL_0001: ldobj "S"
IL_0006: stloc.1
IL_0007: ldloca.s V_1
IL_0009: constrained. "S"
IL_000f: callvirt "System.Collections.Generic.IEnumerator<int> System.Collections.Generic.IEnumerable<int>.GetEnumerator()"
IL_0014: stloc.0
.try
{
IL_0015: br.s IL_001e
IL_0017: ldloc.0
IL_0018: callvirt "int System.Collections.Generic.IEnumerator<int>.Current.get"
IL_001d: pop
IL_001e: ldloc.0
IL_001f: callvirt "bool System.Collections.IEnumerator.MoveNext()"
IL_0024: brtrue.s IL_0017
IL_0026: leave.s IL_0032
}
finally
{
IL_0028: ldloc.0
IL_0029: brfalse.s IL_0031
IL_002b: ldloc.0
IL_002c: callvirt "void System.IDisposable.Dispose()"
IL_0031: endfinally
}
IL_0032: ret
}
""");
}

[Fact, WorkItem(66135, "https://github.com/dotnet/roslyn/issues/66135")]
public void ConstrainedCallOnInParameter_ConstrainedGenericReceiver()
{
var source = @"
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public class C
{
public static void Main()
{
S value = new();
ref readonly S valueRef = ref value;
Console.Write(valueRef);
M(in valueRef);
Console.Write(valueRef);
}
public static void M<T>(in T value) where T : struct, IEnumerable<int>
{
foreach (var x in value) { }
}
}
public struct S : IEnumerable<int>
{
int a;
public readonly override string ToString() => a.ToString();
private IEnumerator<int> GetEnumerator() => Enumerable.Range(0, ++a).GetEnumerator();
IEnumerator<int> IEnumerable<int>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}";
var verifier = CompileAndVerify(source, expectedOutput: "00");
verifier.VerifyIL("C.M<T>(in T)", """
{
// Code size 51 (0x33)
.maxstack 1
.locals init (System.Collections.Generic.IEnumerator<int> V_0,
T V_1)
IL_0000: ldarg.0
IL_0001: ldobj "T"
IL_0006: stloc.1
IL_0007: ldloca.s V_1
IL_0009: constrained. "T"
IL_000f: callvirt "System.Collections.Generic.IEnumerator<int> System.Collections.Generic.IEnumerable<int>.GetEnumerator()"
IL_0014: stloc.0
.try
{
IL_0015: br.s IL_001e
IL_0017: ldloc.0
IL_0018: callvirt "int System.Collections.Generic.IEnumerator<int>.Current.get"
IL_001d: pop
IL_001e: ldloc.0
IL_001f: callvirt "bool System.Collections.IEnumerator.MoveNext()"
IL_0024: brtrue.s IL_0017
IL_0026: leave.s IL_0032
}
finally
{
IL_0028: ldloc.0
IL_0029: brfalse.s IL_0031
IL_002b: ldloc.0
IL_002c: callvirt "void System.IDisposable.Dispose()"
IL_0031: endfinally
}
IL_0032: ret
}
""");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68913,5 +68913,84 @@ event System.Action E2 {add{} remove{}}
Diagnostic(ErrorCode.ERR_ComImportWithImpl, "int").WithArguments("I1.implicit operator int(I1)", "I1").WithLocation(28, 30)
);
}

[Fact, WorkItem(66135, "https://github.com/dotnet/roslyn/issues/66135")]
public void ConstrainedCallOnInParameter_DefaultImplementation()
{
var source = @"
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

public class C
{
public static void Main()
{
S value = new();
ref readonly S valueRef = ref value;
Console.Write(valueRef);
M(in valueRef);
Console.Write(valueRef);
}
public static void M(in S value)
{
foreach (var x in value) { }
}
}

public interface MyEnumerable : IEnumerable<int>
{
IEnumerator<int> GetEnumeratorCore();
IEnumerator<int> IEnumerable<int>.GetEnumerator() => GetEnumeratorCore();
IEnumerator IEnumerable.GetEnumerator() => GetEnumeratorCore();
}

public struct S : MyEnumerable
{
int a;
public readonly override string ToString() => a.ToString();
public IEnumerator<int> GetEnumeratorCore() => Enumerable.Range(0, ++a).GetEnumerator();
}";
var verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net70,
expectedOutput: Execute(isStatic: false) ? "00" : null,
verify: Verify(isStatic: false));

verifier.VerifyIL("C.M", """
{
// Code size 51 (0x33)
.maxstack 1
.locals init (System.Collections.Generic.IEnumerator<int> V_0,
S V_1)
IL_0000: ldarg.0
IL_0001: ldobj "S"
IL_0006: stloc.1
IL_0007: ldloca.s V_1
IL_0009: constrained. "S"
IL_000f: callvirt "System.Collections.Generic.IEnumerator<int> System.Collections.Generic.IEnumerable<int>.GetEnumerator()"
IL_0014: stloc.0
.try
{
IL_0015: br.s IL_001e
IL_0017: ldloc.0
IL_0018: callvirt "int System.Collections.Generic.IEnumerator<int>.Current.get"
IL_001d: pop
IL_001e: ldloc.0
IL_001f: callvirt "bool System.Collections.IEnumerator.MoveNext()"
IL_0024: brtrue.s IL_0017
IL_0026: leave.s IL_0032
}
finally
{
IL_0028: ldloc.0
IL_0029: brfalse.s IL_0031
IL_002b: ldloc.0
IL_002c: callvirt "void System.IDisposable.Dispose()"
IL_0031: endfinally
}
IL_0032: ret
}
""");
}
}
}

0 comments on commit 0085a72

Please sign in to comment.