未验证 提交 69580089 编写于 作者: M Michal Strehovský 提交者: GitHub

Fix runtime dispatch to static virtuals on interface types (#91374)

We were not generating information about static virtuals on interface types. Information about default interface methods normally goes to the class, but if the T we're dispatching on is an interface, this information wasn't generated. The fix is to put this information into dispatch maps and sealed vtables, same way we do for classes.

The test shows what the problem is - if we change `IBar` to be a class, things would work even before this PR.
上级 43f3e60b
...@@ -614,6 +614,8 @@ private static MethodDesc ResolveInterfaceMethodToVirtualMethodOnType(MethodDesc ...@@ -614,6 +614,8 @@ private static MethodDesc ResolveInterfaceMethodToVirtualMethodOnType(MethodDesc
{ {
Debug.Assert(!interfaceMethod.Signature.IsStatic); Debug.Assert(!interfaceMethod.Signature.IsStatic);
// This would be a default interface method resolution. The algorithm below would sort of work, but doesn't handle
// things like diamond cases and it's better not to let it resolve as such.
if (currentType.IsInterface) if (currentType.IsInterface)
return null; return null;
...@@ -781,7 +783,7 @@ private static DefaultInterfaceMethodResolution ResolveInterfaceMethodToDefaultI ...@@ -781,7 +783,7 @@ private static DefaultInterfaceMethodResolution ResolveInterfaceMethodToDefaultI
// If we're asking about an interface, include the interface in the list. // If we're asking about an interface, include the interface in the list.
consideredInterfaces = new DefType[currentType.RuntimeInterfaces.Length + 1]; consideredInterfaces = new DefType[currentType.RuntimeInterfaces.Length + 1];
Array.Copy(currentType.RuntimeInterfaces, consideredInterfaces, currentType.RuntimeInterfaces.Length); Array.Copy(currentType.RuntimeInterfaces, consideredInterfaces, currentType.RuntimeInterfaces.Length);
consideredInterfaces[consideredInterfaces.Length - 1] = (DefType)currentType.InstantiateAsOpen(); consideredInterfaces[consideredInterfaces.Length - 1] = currentType.IsGenericDefinition ? (DefType)currentType.InstantiateAsOpen() : currentType;
} }
foreach (MetadataType runtimeInterface in consideredInterfaces) foreach (MetadataType runtimeInterface in consideredInterfaces)
...@@ -921,6 +923,11 @@ public static IEnumerable<MethodDesc> EnumAllVirtualSlots(MetadataType type) ...@@ -921,6 +923,11 @@ public static IEnumerable<MethodDesc> EnumAllVirtualSlots(MetadataType type)
/// <returns>MethodDesc of the resolved virtual static method, null when not found (runtime lookup must be used)</returns> /// <returns>MethodDesc of the resolved virtual static method, null when not found (runtime lookup must be used)</returns>
public static MethodDesc ResolveInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, MetadataType currentType) public static MethodDesc ResolveInterfaceMethodToStaticVirtualMethodOnType(MethodDesc interfaceMethod, MetadataType currentType)
{ {
// This would be a default interface method resolution. The algorithm below would sort of work, but doesn't handle
// things like diamond cases and it's better not to let it resolve as such.
if (currentType.IsInterface)
return null;
// Search for match on a per-level in the type hierarchy // Search for match on a per-level in the type hierarchy
for (MetadataType typeToCheck = currentType; typeToCheck != null; typeToCheck = typeToCheck.MetadataBaseType) for (MetadataType typeToCheck = currentType; typeToCheck != null; typeToCheck = typeToCheck.MetadataBaseType)
{ {
......
...@@ -372,16 +372,8 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt ...@@ -372,16 +372,8 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt
DefType defType = _type.GetClosestDefType(); DefType defType = _type.GetClosestDefType();
// Interfaces don't have vtables and we don't need to track their slot use.
// The only exception are those interfaces that provide IDynamicInterfaceCastable implementations;
// those have slots and we dispatch on them.
bool needsDependenciesForVirtualMethodImpls = !defType.IsInterface
|| ((MetadataType)defType).IsDynamicInterfaceCastableImplementation();
// If we're producing a full vtable, none of the dependencies are conditional. // If we're producing a full vtable, none of the dependencies are conditional.
needsDependenciesForVirtualMethodImpls &= !factory.VTable(defType).HasFixedSlots; if (!factory.VTable(defType).HasFixedSlots)
if (needsDependenciesForVirtualMethodImpls)
{ {
bool isNonInterfaceAbstractType = !defType.IsInterface && ((MetadataType)defType).IsAbstract; bool isNonInterfaceAbstractType = !defType.IsInterface && ((MetadataType)defType).IsAbstract;
...@@ -436,6 +428,12 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt ...@@ -436,6 +428,12 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt
((System.Collections.IStructuralEquatable)defType.RuntimeInterfaces).Equals(_type.RuntimeInterfaces, ((System.Collections.IStructuralEquatable)defType.RuntimeInterfaces).Equals(_type.RuntimeInterfaces,
EqualityComparer<DefType>.Default)); EqualityComparer<DefType>.Default));
// Interfaces don't have vtables and we don't need to track their instance method slot use.
// The only exception are those interfaces that provide IDynamicInterfaceCastable implementations;
// those have slots and we dispatch on them.
bool needsDependenciesForInstanceInterfaceMethodImpls = !defType.IsInterface
|| ((MetadataType)defType).IsDynamicInterfaceCastableImplementation();
// Add conditional dependencies for interface methods the type implements. For example, if the type T implements // Add conditional dependencies for interface methods the type implements. For example, if the type T implements
// interface IFoo which has a method M1, add a dependency on T.M1 dependent on IFoo.M1 being called, since it's // interface IFoo which has a method M1, add a dependency on T.M1 dependent on IFoo.M1 being called, since it's
// possible for any IFoo object to actually be an instance of T. // possible for any IFoo object to actually be an instance of T.
...@@ -456,6 +454,9 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt ...@@ -456,6 +454,9 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt
bool isStaticInterfaceMethod = interfaceMethod.Signature.IsStatic; bool isStaticInterfaceMethod = interfaceMethod.Signature.IsStatic;
if (!isStaticInterfaceMethod && !needsDependenciesForInstanceInterfaceMethodImpls)
continue;
MethodDesc implMethod = isStaticInterfaceMethod ? MethodDesc implMethod = isStaticInterfaceMethod ?
defType.ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod) : defType.ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod) :
defType.ResolveInterfaceMethodToVirtualMethodOnType(interfaceMethod); defType.ResolveInterfaceMethodToVirtualMethodOnType(interfaceMethod);
......
...@@ -73,7 +73,7 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact ...@@ -73,7 +73,7 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact
if (!type.IsArray && !type.IsDefType) if (!type.IsArray && !type.IsDefType)
return false; return false;
// Interfaces don't have a dispatch map because we dispatch them based on the // Interfaces don't have a dispatch map for instance methods because we dispatch them based on the
// dispatch map of the implementing class. // dispatch map of the implementing class.
// The only exception are IDynamicInterfaceCastable scenarios that dispatch // The only exception are IDynamicInterfaceCastable scenarios that dispatch
// using the interface dispatch map. // using the interface dispatch map.
...@@ -83,8 +83,9 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact ...@@ -83,8 +83,9 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact
// wasn't marked as [DynamicInterfaceCastableImplementation]" and "we couldn't find an // wasn't marked as [DynamicInterfaceCastableImplementation]" and "we couldn't find an
// implementation". We don't want to use the custom attribute for that at runtime because // implementation". We don't want to use the custom attribute for that at runtime because
// that's reflection and this should work without reflection. // that's reflection and this should work without reflection.
if (type.IsInterface) bool isInterface = type.IsInterface;
return ((MetadataType)type).IsDynamicInterfaceCastableImplementation(); if (isInterface && ((MetadataType)type).IsDynamicInterfaceCastableImplementation())
return true;
DefType declType = type.GetClosestDefType(); DefType declType = type.GetClosestDefType();
...@@ -112,6 +113,11 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact ...@@ -112,6 +113,11 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact
Debug.Assert(declMethod.IsVirtual); Debug.Assert(declMethod.IsVirtual);
// Only static methods get placed in dispatch maps of interface types (modulo
// IDynamicInterfaceCastable we already handled above).
if (isInterface && !declMethod.Signature.IsStatic)
continue;
if (interfaceOnDefinitionType != null) if (interfaceOnDefinitionType != null)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), interfaceOnDefinitionType); declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), interfaceOnDefinitionType);
...@@ -154,6 +160,10 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory) ...@@ -154,6 +160,10 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
var staticImplementations = new List<(int InterfaceIndex, int InterfaceMethodSlot, int ImplMethodSlot, int Context)>(); var staticImplementations = new List<(int InterfaceIndex, int InterfaceMethodSlot, int ImplMethodSlot, int Context)>();
var staticDefaultImplementations = new List<(int InterfaceIndex, int InterfaceMethodSlot, int ImplMethodSlot, int Context)>(); var staticDefaultImplementations = new List<(int InterfaceIndex, int InterfaceMethodSlot, int ImplMethodSlot, int Context)>();
bool isInterface = declType.IsInterface;
bool needsEntriesForInstanceInterfaceMethodImpls = !isInterface
|| ((MetadataType)declType).IsDynamicInterfaceCastableImplementation();
// Resolve all the interfaces, but only emit non-static and non-default implementations // Resolve all the interfaces, but only emit non-static and non-default implementations
for (int interfaceIndex = 0; interfaceIndex < declTypeRuntimeInterfaces.Length; interfaceIndex++) for (int interfaceIndex = 0; interfaceIndex < declTypeRuntimeInterfaces.Length; interfaceIndex++)
{ {
...@@ -166,6 +176,10 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory) ...@@ -166,6 +176,10 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++) for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++)
{ {
MethodDesc declMethod = virtualSlots[interfaceMethodSlot]; MethodDesc declMethod = virtualSlots[interfaceMethodSlot];
if (!declMethod.Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;
if(!interfaceType.IsTypeDefinition) if(!interfaceType.IsTypeDefinition)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType); declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType);
...@@ -244,9 +258,17 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory) ...@@ -244,9 +258,17 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
// For default interface methods, the generic context is acquired by indexing // For default interface methods, the generic context is acquired by indexing
// into the interface list of the owning type. // into the interface list of the owning type.
Debug.Assert(providingInterfaceDefinitionType != null); Debug.Assert(providingInterfaceDefinitionType != null);
int indexOfInterface = Array.IndexOf(declTypeDefinitionRuntimeInterfaces, providingInterfaceDefinitionType); if (declTypeDefinition.HasSameTypeDefinition(providingInterfaceDefinitionType) &&
Debug.Assert(indexOfInterface >= 0); providingInterfaceDefinitionType == declTypeDefinition.InstantiateAsOpen())
genericContext = StaticVirtualMethodContextSource.ContextFromFirstInterface + indexOfInterface; {
genericContext = StaticVirtualMethodContextSource.ContextFromThisClass;
}
else
{
int indexOfInterface = Array.IndexOf(declTypeDefinitionRuntimeInterfaces, providingInterfaceDefinitionType);
Debug.Assert(indexOfInterface >= 0);
genericContext = StaticVirtualMethodContextSource.ContextFromFirstInterface + indexOfInterface;
}
} }
staticDefaultImplementations.Add(( staticDefaultImplementations.Add((
interfaceIndex, interfaceIndex,
......
...@@ -108,17 +108,21 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly) ...@@ -108,17 +108,21 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)
_sealedVTableEntries = new List<SealedVTableEntry>(); _sealedVTableEntries = new List<SealedVTableEntry>();
// Interfaces don't have any virtual slots with the exception of interfaces that provide // Interfaces don't have any instance virtual slots with the exception of interfaces that provide
// IDynamicInterfaceCastable implementation. // IDynamicInterfaceCastable implementation.
// Normal interface don't need one because the dispatch is done at the class level. // Normal interface don't need one because the dispatch is done at the class level.
// For IDynamicInterfaceCastable, we don't have an implementing class. // For IDynamicInterfaceCastable, we don't have an implementing class.
if (_type.IsInterface && !((MetadataType)_type).IsDynamicInterfaceCastableImplementation()) bool isInterface = declType.IsInterface;
return true; bool needsEntriesForInstanceInterfaceMethodImpls = !isInterface
|| ((MetadataType)declType).IsDynamicInterfaceCastableImplementation();
IReadOnlyList<MethodDesc> virtualSlots = factory.VTable(declType).Slots; IReadOnlyList<MethodDesc> virtualSlots = factory.VTable(declType).Slots;
for (int i = 0; i < virtualSlots.Count; i++) for (int i = 0; i < virtualSlots.Count; i++)
{ {
if (!virtualSlots[i].Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;
MethodDesc implMethod = declType.FindVirtualFunctionTargetMethodOnObjectType(virtualSlots[i]); MethodDesc implMethod = declType.FindVirtualFunctionTargetMethodOnObjectType(virtualSlots[i]);
if (implMethod.CanMethodBeInSealedVTable()) if (implMethod.CanMethodBeInSealedVTable())
...@@ -143,6 +147,10 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly) ...@@ -143,6 +147,10 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)
for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++) for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++)
{ {
MethodDesc declMethod = virtualSlots[interfaceMethodSlot]; MethodDesc declMethod = virtualSlots[interfaceMethodSlot];
if (!declMethod.Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;
if (!interfaceType.IsTypeDefinition) if (!interfaceType.IsTypeDefinition)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType); declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType);
......
...@@ -93,9 +93,8 @@ private static int GetNumberOfSlotsInCurrentType(NodeFactory factory, TypeDesc i ...@@ -93,9 +93,8 @@ private static int GetNumberOfSlotsInCurrentType(NodeFactory factory, TypeDesc i
{ {
if (implType.IsInterface) if (implType.IsInterface)
{ {
// We normally don't need to ask about vtable slots of interfaces. It's not wrong to ask // Interface types don't have physically assigned virtual slots, so the number of slots
// that question, but we currently only ask it for IDynamicInterfaceCastable implementations. // is always 0. They may have sealed slots.
Debug.Assert(((MetadataType)implType).IsDynamicInterfaceCastableImplementation());
return (implType.HasGenericDictionarySlot() && countDictionarySlots) ? 1 : 0; return (implType.HasGenericDictionarySlot() && countDictionarySlots) ? 1 : 0;
} }
......
...@@ -52,6 +52,8 @@ public static int Run() ...@@ -52,6 +52,8 @@ public static int Run()
TestMoreConstraints.Run(); TestMoreConstraints.Run();
TestSimpleNonGeneric.Run(); TestSimpleNonGeneric.Run();
TestSimpleGeneric.Run(); TestSimpleGeneric.Run();
TestDefaultDynamicStaticNonGeneric.Run();
TestDefaultDynamicStaticGeneric.Run();
TestDynamicStaticGenericVirtualMethods.Run(); TestDynamicStaticGenericVirtualMethods.Run();
return Pass; return Pass;
...@@ -1502,6 +1504,77 @@ public static void Run() ...@@ -1502,6 +1504,77 @@ public static void Run()
} }
} }
class TestDefaultDynamicStaticNonGeneric
{
interface IFoo
{
abstract static string ImHungryGiveMeCookie();
}
interface IBar : IFoo
{
static string IFoo.ImHungryGiveMeCookie() => "IBar";
}
class Baz : IBar
{
}
class Gen<T> where T : IFoo
{
public static string GrabCookie() => T.ImHungryGiveMeCookie();
}
public static void Run()
{
var r = (string)typeof(Gen<>).MakeGenericType(typeof(Baz)).GetMethod("GrabCookie").Invoke(null, Array.Empty<object>());
if (r != "IBar")
throw new Exception(r);
r = (string)typeof(Gen<>).MakeGenericType(typeof(IBar)).GetMethod("GrabCookie").Invoke(null, Array.Empty<object>());
if (r != "IBar")
throw new Exception(r);
}
}
class TestDefaultDynamicStaticGeneric
{
class Atom1 { }
class Atom2 { }
interface IFoo
{
abstract static string ImHungryGiveMeCookie();
}
interface IBar<T> : IFoo
{
static string IFoo.ImHungryGiveMeCookie() => $"IBar<{typeof(T).Name}>";
}
class Baz<T> : IBar<T>
{
}
class Gen<T> where T : IFoo
{
public static string GrabCookie() => T.ImHungryGiveMeCookie();
}
public static void Run()
{
Activator.CreateInstance(typeof(Baz<>).MakeGenericType(typeof(Atom1)));
var r = (string)typeof(Gen<>).MakeGenericType(typeof(Baz<>).MakeGenericType(typeof(Atom1))).GetMethod("GrabCookie").Invoke(null, Array.Empty<object>());
if (r != "IBar<Atom1>")
throw new Exception(r);
r = (string)typeof(Gen<>).MakeGenericType(typeof(IBar<>).MakeGenericType(typeof(Atom2))).GetMethod("GrabCookie").Invoke(null, Array.Empty<object>());
if (r != "IBar<Atom2>")
throw new Exception(r);
}
}
class TestDynamicStaticGenericVirtualMethods class TestDynamicStaticGenericVirtualMethods
{ {
interface IEntry interface IEntry
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册