Skip to content

Commit d0bdc78

Browse files
authored
Fix issue with array interface devirtualization (#120313)
The instantiated methods should use the parameter type of the interface, not the element type of the array. This means we also fail to devirtualize an ref-type array to a shared interface type, which causes us to lose some of the performance benefits and revert to .net 9 behavior. Fixes #120270.
1 parent ae21049 commit d0bdc78

File tree

3 files changed

+118
-15
lines changed

3 files changed

+118
-15
lines changed

src/coreclr/vm/jitinterface.cpp

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8604,18 +8604,18 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info)
86048604

86058605
if (pObjMT->IsArray())
86068606
{
8607+
if (pBaseMT->IsSharedByGenericInstantiations())
8608+
{
8609+
info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON;
8610+
return false;
8611+
}
8612+
86078613
// Does the array implicitly implement this interface?
86088614
//
86098615
isArrayImplicitInterface = pBaseMT->HasInstantiation() && IsImplicitInterfaceOfSZArray(pBaseMT);
86108616

86118617
if (!isArrayImplicitInterface)
86128618
{
8613-
if (pBaseMT->IsSharedByGenericInstantiations())
8614-
{
8615-
info->detail = CORINFO_DEVIRTUALIZATION_FAILED_CANON;
8616-
return false;
8617-
}
8618-
86198619
// Ensure we can cast the array to the interface type
86208620
//
86218621
if (!TypeHandle(pObjMT).CanCastTo(TypeHandle(pBaseMT)))
@@ -8641,15 +8641,12 @@ bool CEEInfo::resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info)
86418641
{
86428642
_ASSERTE(pObjMT->IsArray());
86438643

8644-
// We cannot devirtualize unless we know the exact array element type
8645-
//
8646-
TypeHandle elemType = pObjMT->GetArrayElementTypeHandle();
8647-
if (elemType.IsCanonicalSubtype())
8648-
{
8649-
info->detail = CORINFO_DEVIRTUALIZATION_FAILED_LOOKUP;
8650-
return false;
8651-
}
8652-
pDevirtMD = GetActualImplementationForArrayGenericIListOrIReadOnlyListMethod(pBaseMD, elemType);
8644+
// The instantiation we want is based on the interface element type, not the
8645+
// array element type.
8646+
TypeHandle resultElemType = pBaseMT->GetInstantiation()[0];
8647+
// We should have ruled this out above.
8648+
_ASSERTE(!resultElemType.IsCanonicalSubtype());
8649+
pDevirtMD = GetActualImplementationForArrayGenericIListOrIReadOnlyListMethod(pBaseMD, resultElemType);
86538650
}
86548651
else if (pObjMT->IsSharedByGenericInstantiations() || pBaseMT->IsSharedByGenericInstantiations())
86558652
{
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Runtime.CompilerServices;
8+
using Xunit;
9+
10+
class Base{}
11+
class Derived : Base {}
12+
13+
public class Runtime_120270
14+
{
15+
[Fact]
16+
public static int TestEntryPoint()
17+
{
18+
int i = 0;
19+
bool failed = false;
20+
try
21+
{
22+
while (i < 100_000)
23+
{
24+
i++;
25+
var values = new[] { DayOfWeek.Saturday }.Cast<int>();
26+
foreach (var value in values)
27+
{
28+
}
29+
}
30+
}
31+
catch (Exception ex)
32+
{
33+
Console.WriteLine(i);
34+
Console.WriteLine(ex);
35+
failed = true;
36+
}
37+
38+
return failed ? -1 : 100;
39+
}
40+
41+
[Fact]
42+
public static int TestEntryPoint2()
43+
{
44+
bool failed = false;
45+
try
46+
{
47+
Foo<Base>([new Derived()]);
48+
}
49+
catch (Exception ex)
50+
{
51+
Console.WriteLine(ex);
52+
failed = true;
53+
}
54+
return failed ? -1 : 100;
55+
}
56+
57+
[MethodImpl(MethodImplOptions.NoInlining)]
58+
static void Foo<T>(object[] x)
59+
{
60+
int i = 0;
61+
while (i < 100_000)
62+
{
63+
i++;
64+
var values = x.Cast<T>();
65+
foreach (var value in values)
66+
{
67+
}
68+
}
69+
}
70+
71+
[Fact]
72+
public static int TestEntryPoint3()
73+
{
74+
Type enumeratorType = null;
75+
for (int i = 0; i < 100_000; i++)
76+
{
77+
Derived[] d = [new Derived()];
78+
IEnumerable<Base> e = d;
79+
IEnumerator<Base> en = e.GetEnumerator();
80+
Type currentEnumeratorType = en.GetType();
81+
82+
if (i == 0)
83+
{
84+
Console.WriteLine($"Enumerator type: {currentEnumeratorType.ToString()}");
85+
enumeratorType = currentEnumeratorType;
86+
}
87+
88+
if (enumeratorType != currentEnumeratorType)
89+
{
90+
Console.WriteLine($"Enumerator type changed at {i} from {enumeratorType.ToString()} to {currentEnumeratorType.ToString()}");
91+
return -1;
92+
}
93+
}
94+
return 100;
95+
}
96+
}
97+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<DebugType>None</DebugType>
4+
<Optimize>True</Optimize>
5+
</PropertyGroup>
6+
<ItemGroup>
7+
<Compile Include="$(MSBuildProjectName).cs" />
8+
</ItemGroup>
9+
</Project>

0 commit comments

Comments
 (0)