Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -614,6 +614,8 @@ private static MethodDesc ResolveInterfaceMethodToVirtualMethodOnType(MethodDesc
{
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)
return null;

Expand DownExpand Up@@ -781,7 +783,7 @@ private static DefaultInterfaceMethodResolution ResolveInterfaceMethodToDefaultI
// If we're asking about an interface, include the interface in the list.
consideredInterfaces = new DefType[currentType.RuntimeInterfaces.Length + 1];
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)
Expand DownExpand Up@@ -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>
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
for (MetadataType typeToCheck = currentType; typeToCheck != null; typeToCheck = typeToCheck.MetadataBaseType)
{
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -372,16 +372,8 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt

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.
needsDependenciesForVirtualMethodImpls &= !factory.VTable(defType).HasFixedSlots;

if (needsDependenciesForVirtualMethodImpls)
if (!factory.VTable(defType).HasFixedSlots)
{
bool isNonInterfaceAbstractType = !defType.IsInterface && ((MetadataType)defType).IsAbstract;

Expand DownExpand Up@@ -436,6 +428,12 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt
((System.Collections.IStructuralEquatable)defType.RuntimeInterfaces).Equals(_type.RuntimeInterfaces,
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
// 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.
Expand All@@ -456,6 +454,9 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt

bool isStaticInterfaceMethod = interfaceMethod.Signature.IsStatic;

if (!isStaticInterfaceMethod && !needsDependenciesForInstanceInterfaceMethodImpls)
continue;

MethodDesc implMethod = isStaticInterfaceMethod ?
defType.ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod) :
defType.ResolveInterfaceMethodToVirtualMethodOnType(interfaceMethod);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,7 +73,7 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact
if (!type.IsArray && !type.IsDefType)
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.
// The only exception are IDynamicInterfaceCastable scenarios that dispatch
// using the interface dispatch map.
Expand All@@ -83,8 +83,9 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact
// 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
// that's reflection and this should work without reflection.
if (type.IsInterface)
return ((MetadataType)type).IsDynamicInterfaceCastableImplementation();
bool isInterface = type.IsInterface;
if (isInterface && ((MetadataType)type).IsDynamicInterfaceCastableImplementation())
return true;

DefType declType = type.GetClosestDefType();

Expand DownExpand Up@@ -112,6 +113,11 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact

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)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), interfaceOnDefinitionType);

Expand DownExpand Up@@ -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 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
for (int interfaceIndex = 0; interfaceIndex < declTypeRuntimeInterfaces.Length; interfaceIndex++)
{
Expand All@@ -166,6 +176,10 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++)
{
MethodDesc declMethod = virtualSlots[interfaceMethodSlot];

if (!declMethod.Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

if(!interfaceType.IsTypeDefinition)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType);

Expand DownExpand Up@@ -244,9 +258,17 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
// For default interface methods, the generic context is acquired by indexing
// into the interface list of the owning type.
Debug.Assert(providingInterfaceDefinitionType != null);
int indexOfInterface = Array.IndexOf(declTypeDefinitionRuntimeInterfaces, providingInterfaceDefinitionType);
Debug.Assert(indexOfInterface >= 0);
genericContext = StaticVirtualMethodContextSource.ContextFromFirstInterface + indexOfInterface;
if (declTypeDefinition.HasSameTypeDefinition(providingInterfaceDefinitionType) &&
providingInterfaceDefinitionType == declTypeDefinition.InstantiateAsOpen())
{
genericContext = StaticVirtualMethodContextSource.ContextFromThisClass;
}
else
{
int indexOfInterface = Array.IndexOf(declTypeDefinitionRuntimeInterfaces, providingInterfaceDefinitionType);
Debug.Assert(indexOfInterface >= 0);
genericContext = StaticVirtualMethodContextSource.ContextFromFirstInterface + indexOfInterface;
}
}
staticDefaultImplementations.Add((
interfaceIndex,
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,17 +108,21 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)

_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.
// Normal interface don't need one because the dispatch is done at the class level.
// For IDynamicInterfaceCastable, we don't have an implementing class.
if (_type.IsInterface && !((MetadataType)_type).IsDynamicInterfaceCastableImplementation())
return true;
bool isInterface = declType.IsInterface;
bool needsEntriesForInstanceInterfaceMethodImpls = !isInterface
|| ((MetadataType)declType).IsDynamicInterfaceCastableImplementation();

IReadOnlyList<MethodDesc> virtualSlots = factory.VTable(declType).Slots;

for (int i = 0; i < virtualSlots.Count; i++)
{
if (!virtualSlots[i].Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

MethodDesc implMethod = declType.FindVirtualFunctionTargetMethodOnObjectType(virtualSlots[i]);

if (implMethod.CanMethodBeInSealedVTable())
Expand All@@ -143,6 +147,10 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)
for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++)
{
MethodDesc declMethod = virtualSlots[interfaceMethodSlot];

if (!declMethod.Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

if (!interfaceType.IsTypeDefinition)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType);

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,9 +93,8 @@ private static int GetNumberOfSlotsInCurrentType(NodeFactory factory, TypeDesc i
{
if (implType.IsInterface)
{
// We normally don't need to ask about vtable slots of interfaces. It's not wrong to ask
// that question, but we currently only ask it for IDynamicInterfaceCastable implementations.
Debug.Assert(((MetadataType)implType).IsDynamicInterfaceCastableImplementation());
// Interface types don't have physically assigned virtual slots, so the number of slots
// is always 0. They may have sealed slots.
return (implType.HasGenericDictionarySlot() && countDictionarySlots) ? 1 : 0;
}

Expand Down
73 changes: 73 additions & 0 deletions src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,6 +52,8 @@ public static int Run()
TestMoreConstraints.Run();
TestSimpleNonGeneric.Run();
TestSimpleGeneric.Run();
TestDefaultDynamicStaticNonGeneric.Run();
TestDefaultDynamicStaticGeneric.Run();
TestDynamicStaticGenericVirtualMethods.Run();

return Pass;
Expand DownExpand Up@@ -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
{
interface IEntry
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
[release/8.0] Fix runtime dispatch to static virtuals on interface types by github-actions[bot] · Pull Request #91440 · dotnet/runtime · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -614,6 +614,8 @@ private static MethodDesc ResolveInterfaceMethodToVirtualMethodOnType(MethodDesc
{
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)
return null;

Expand DownExpand Up@@ -781,7 +783,7 @@ private static DefaultInterfaceMethodResolution ResolveInterfaceMethodToDefaultI
// If we're asking about an interface, include the interface in the list.
consideredInterfaces = new DefType[currentType.RuntimeInterfaces.Length + 1];
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)
Expand DownExpand Up@@ -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>
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
for (MetadataType typeToCheck = currentType; typeToCheck != null; typeToCheck = typeToCheck.MetadataBaseType)
{
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -372,16 +372,8 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt

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.
needsDependenciesForVirtualMethodImpls &= !factory.VTable(defType).HasFixedSlots;

if (needsDependenciesForVirtualMethodImpls)
if (!factory.VTable(defType).HasFixedSlots)
{
bool isNonInterfaceAbstractType = !defType.IsInterface && ((MetadataType)defType).IsAbstract;

Expand DownExpand Up@@ -436,6 +428,12 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt
((System.Collections.IStructuralEquatable)defType.RuntimeInterfaces).Equals(_type.RuntimeInterfaces,
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
// 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.
Expand All@@ -456,6 +454,9 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt

bool isStaticInterfaceMethod = interfaceMethod.Signature.IsStatic;

if (!isStaticInterfaceMethod && !needsDependenciesForInstanceInterfaceMethodImpls)
continue;

MethodDesc implMethod = isStaticInterfaceMethod ?
defType.ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod) :
defType.ResolveInterfaceMethodToVirtualMethodOnType(interfaceMethod);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,7 +73,7 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact
if (!type.IsArray && !type.IsDefType)
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.
// The only exception are IDynamicInterfaceCastable scenarios that dispatch
// using the interface dispatch map.
Expand All@@ -83,8 +83,9 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact
// 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
// that's reflection and this should work without reflection.
if (type.IsInterface)
return ((MetadataType)type).IsDynamicInterfaceCastableImplementation();
bool isInterface = type.IsInterface;
if (isInterface && ((MetadataType)type).IsDynamicInterfaceCastableImplementation())
return true;

DefType declType = type.GetClosestDefType();

Expand DownExpand Up@@ -112,6 +113,11 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact

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)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), interfaceOnDefinitionType);

Expand DownExpand Up@@ -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 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
for (int interfaceIndex = 0; interfaceIndex < declTypeRuntimeInterfaces.Length; interfaceIndex++)
{
Expand All@@ -166,6 +176,10 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++)
{
MethodDesc declMethod = virtualSlots[interfaceMethodSlot];

if (!declMethod.Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

if(!interfaceType.IsTypeDefinition)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType);

Expand DownExpand Up@@ -244,9 +258,17 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
// For default interface methods, the generic context is acquired by indexing
// into the interface list of the owning type.
Debug.Assert(providingInterfaceDefinitionType != null);
int indexOfInterface = Array.IndexOf(declTypeDefinitionRuntimeInterfaces, providingInterfaceDefinitionType);
Debug.Assert(indexOfInterface >= 0);
genericContext = StaticVirtualMethodContextSource.ContextFromFirstInterface + indexOfInterface;
if (declTypeDefinition.HasSameTypeDefinition(providingInterfaceDefinitionType) &&
providingInterfaceDefinitionType == declTypeDefinition.InstantiateAsOpen())
{
genericContext = StaticVirtualMethodContextSource.ContextFromThisClass;
}
else
{
int indexOfInterface = Array.IndexOf(declTypeDefinitionRuntimeInterfaces, providingInterfaceDefinitionType);
Debug.Assert(indexOfInterface >= 0);
genericContext = StaticVirtualMethodContextSource.ContextFromFirstInterface + indexOfInterface;
}
}
staticDefaultImplementations.Add((
interfaceIndex,
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,17 +108,21 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)

_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.
// Normal interface don't need one because the dispatch is done at the class level.
// For IDynamicInterfaceCastable, we don't have an implementing class.
if (_type.IsInterface && !((MetadataType)_type).IsDynamicInterfaceCastableImplementation())
return true;
bool isInterface = declType.IsInterface;
bool needsEntriesForInstanceInterfaceMethodImpls = !isInterface
|| ((MetadataType)declType).IsDynamicInterfaceCastableImplementation();

IReadOnlyList<MethodDesc> virtualSlots = factory.VTable(declType).Slots;

for (int i = 0; i < virtualSlots.Count; i++)
{
if (!virtualSlots[i].Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

MethodDesc implMethod = declType.FindVirtualFunctionTargetMethodOnObjectType(virtualSlots[i]);

if (implMethod.CanMethodBeInSealedVTable())
Expand All@@ -143,6 +147,10 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)
for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++)
{
MethodDesc declMethod = virtualSlots[interfaceMethodSlot];

if (!declMethod.Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

if (!interfaceType.IsTypeDefinition)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType);

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,9 +93,8 @@ private static int GetNumberOfSlotsInCurrentType(NodeFactory factory, TypeDesc i
{
if (implType.IsInterface)
{
// We normally don't need to ask about vtable slots of interfaces. It's not wrong to ask
// that question, but we currently only ask it for IDynamicInterfaceCastable implementations.
Debug.Assert(((MetadataType)implType).IsDynamicInterfaceCastableImplementation());
// Interface types don't have physically assigned virtual slots, so the number of slots
// is always 0. They may have sealed slots.
return (implType.HasGenericDictionarySlot() && countDictionarySlots) ? 1 : 0;
}

Expand Down
73 changes: 73 additions & 0 deletions src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,6 +52,8 @@ public static int Run()
TestMoreConstraints.Run();
TestSimpleNonGeneric.Run();
TestSimpleGeneric.Run();
TestDefaultDynamicStaticNonGeneric.Run();
TestDefaultDynamicStaticGeneric.Run();
TestDynamicStaticGenericVirtualMethods.Run();

return Pass;
Expand DownExpand Up@@ -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
{
interface IEntry
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [release/8.0] Fix runtime dispatch to static virtuals on interface types by github-actions[bot] · Pull Request #91440 · dotnet/runtime · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -614,6 +614,8 @@ private static MethodDesc ResolveInterfaceMethodToVirtualMethodOnType(MethodDesc
{
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)
return null;

Expand DownExpand Up@@ -781,7 +783,7 @@ private static DefaultInterfaceMethodResolution ResolveInterfaceMethodToDefaultI
// If we're asking about an interface, include the interface in the list.
consideredInterfaces = new DefType[currentType.RuntimeInterfaces.Length + 1];
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)
Expand DownExpand Up@@ -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>
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
for (MetadataType typeToCheck = currentType; typeToCheck != null; typeToCheck = typeToCheck.MetadataBaseType)
{
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -372,16 +372,8 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt

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.
needsDependenciesForVirtualMethodImpls &= !factory.VTable(defType).HasFixedSlots;

if (needsDependenciesForVirtualMethodImpls)
if (!factory.VTable(defType).HasFixedSlots)
{
bool isNonInterfaceAbstractType = !defType.IsInterface && ((MetadataType)defType).IsAbstract;

Expand DownExpand Up@@ -436,6 +428,12 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt
((System.Collections.IStructuralEquatable)defType.RuntimeInterfaces).Equals(_type.RuntimeInterfaces,
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
// 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.
Expand All@@ -456,6 +454,9 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt

bool isStaticInterfaceMethod = interfaceMethod.Signature.IsStatic;

if (!isStaticInterfaceMethod && !needsDependenciesForInstanceInterfaceMethodImpls)
continue;

MethodDesc implMethod = isStaticInterfaceMethod ?
defType.ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod) :
defType.ResolveInterfaceMethodToVirtualMethodOnType(interfaceMethod);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,7 +73,7 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact
if (!type.IsArray && !type.IsDefType)
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.
// The only exception are IDynamicInterfaceCastable scenarios that dispatch
// using the interface dispatch map.
Expand All@@ -83,8 +83,9 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact
// 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
// that's reflection and this should work without reflection.
if (type.IsInterface)
return ((MetadataType)type).IsDynamicInterfaceCastableImplementation();
bool isInterface = type.IsInterface;
if (isInterface && ((MetadataType)type).IsDynamicInterfaceCastableImplementation())
return true;

DefType declType = type.GetClosestDefType();

Expand DownExpand Up@@ -112,6 +113,11 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact

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)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), interfaceOnDefinitionType);

Expand DownExpand Up@@ -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 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
for (int interfaceIndex = 0; interfaceIndex < declTypeRuntimeInterfaces.Length; interfaceIndex++)
{
Expand All@@ -166,6 +176,10 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++)
{
MethodDesc declMethod = virtualSlots[interfaceMethodSlot];

if (!declMethod.Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

if(!interfaceType.IsTypeDefinition)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType);

Expand DownExpand Up@@ -244,9 +258,17 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
// For default interface methods, the generic context is acquired by indexing
// into the interface list of the owning type.
Debug.Assert(providingInterfaceDefinitionType != null);
int indexOfInterface = Array.IndexOf(declTypeDefinitionRuntimeInterfaces, providingInterfaceDefinitionType);
Debug.Assert(indexOfInterface >= 0);
genericContext = StaticVirtualMethodContextSource.ContextFromFirstInterface + indexOfInterface;
if (declTypeDefinition.HasSameTypeDefinition(providingInterfaceDefinitionType) &&
providingInterfaceDefinitionType == declTypeDefinition.InstantiateAsOpen())
{
genericContext = StaticVirtualMethodContextSource.ContextFromThisClass;
}
else
{
int indexOfInterface = Array.IndexOf(declTypeDefinitionRuntimeInterfaces, providingInterfaceDefinitionType);
Debug.Assert(indexOfInterface >= 0);
genericContext = StaticVirtualMethodContextSource.ContextFromFirstInterface + indexOfInterface;
}
}
staticDefaultImplementations.Add((
interfaceIndex,
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,17 +108,21 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)

_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.
// Normal interface don't need one because the dispatch is done at the class level.
// For IDynamicInterfaceCastable, we don't have an implementing class.
if (_type.IsInterface && !((MetadataType)_type).IsDynamicInterfaceCastableImplementation())
return true;
bool isInterface = declType.IsInterface;
bool needsEntriesForInstanceInterfaceMethodImpls = !isInterface
|| ((MetadataType)declType).IsDynamicInterfaceCastableImplementation();

IReadOnlyList<MethodDesc> virtualSlots = factory.VTable(declType).Slots;

for (int i = 0; i < virtualSlots.Count; i++)
{
if (!virtualSlots[i].Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

MethodDesc implMethod = declType.FindVirtualFunctionTargetMethodOnObjectType(virtualSlots[i]);

if (implMethod.CanMethodBeInSealedVTable())
Expand All@@ -143,6 +147,10 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)
for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++)
{
MethodDesc declMethod = virtualSlots[interfaceMethodSlot];

if (!declMethod.Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

if (!interfaceType.IsTypeDefinition)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType);

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,9 +93,8 @@ private static int GetNumberOfSlotsInCurrentType(NodeFactory factory, TypeDesc i
{
if (implType.IsInterface)
{
// We normally don't need to ask about vtable slots of interfaces. It's not wrong to ask
// that question, but we currently only ask it for IDynamicInterfaceCastable implementations.
Debug.Assert(((MetadataType)implType).IsDynamicInterfaceCastableImplementation());
// Interface types don't have physically assigned virtual slots, so the number of slots
// is always 0. They may have sealed slots.
return (implType.HasGenericDictionarySlot() && countDictionarySlots) ? 1 : 0;
}

Expand Down
73 changes: 73 additions & 0 deletions src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,6 +52,8 @@ public static int Run()
TestMoreConstraints.Run();
TestSimpleNonGeneric.Run();
TestSimpleGeneric.Run();
TestDefaultDynamicStaticNonGeneric.Run();
TestDefaultDynamicStaticGeneric.Run();
TestDynamicStaticGenericVirtualMethods.Run();

return Pass;
Expand DownExpand Up@@ -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
{
interface IEntry
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [release/8.0] Fix runtime dispatch to static virtuals on interface types by github-actions[bot] · Pull Request #91440 · dotnet/runtime · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -614,6 +614,8 @@ private static MethodDesc ResolveInterfaceMethodToVirtualMethodOnType(MethodDesc
{
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)
return null;

Expand DownExpand Up@@ -781,7 +783,7 @@ private static DefaultInterfaceMethodResolution ResolveInterfaceMethodToDefaultI
// If we're asking about an interface, include the interface in the list.
consideredInterfaces = new DefType[currentType.RuntimeInterfaces.Length + 1];
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)
Expand DownExpand Up@@ -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>
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
for (MetadataType typeToCheck = currentType; typeToCheck != null; typeToCheck = typeToCheck.MetadataBaseType)
{
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -372,16 +372,8 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt

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.
needsDependenciesForVirtualMethodImpls &= !factory.VTable(defType).HasFixedSlots;

if (needsDependenciesForVirtualMethodImpls)
if (!factory.VTable(defType).HasFixedSlots)
{
bool isNonInterfaceAbstractType = !defType.IsInterface && ((MetadataType)defType).IsAbstract;

Expand DownExpand Up@@ -436,6 +428,12 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt
((System.Collections.IStructuralEquatable)defType.RuntimeInterfaces).Equals(_type.RuntimeInterfaces,
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
// 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.
Expand All@@ -456,6 +454,9 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt

bool isStaticInterfaceMethod = interfaceMethod.Signature.IsStatic;

if (!isStaticInterfaceMethod && !needsDependenciesForInstanceInterfaceMethodImpls)
continue;

MethodDesc implMethod = isStaticInterfaceMethod ?
defType.ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod) :
defType.ResolveInterfaceMethodToVirtualMethodOnType(interfaceMethod);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,7 +73,7 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact
if (!type.IsArray && !type.IsDefType)
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.
// The only exception are IDynamicInterfaceCastable scenarios that dispatch
// using the interface dispatch map.
Expand All@@ -83,8 +83,9 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact
// 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
// that's reflection and this should work without reflection.
if (type.IsInterface)
return ((MetadataType)type).IsDynamicInterfaceCastableImplementation();
bool isInterface = type.IsInterface;
if (isInterface && ((MetadataType)type).IsDynamicInterfaceCastableImplementation())
return true;

DefType declType = type.GetClosestDefType();

Expand DownExpand Up@@ -112,6 +113,11 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact

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)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), interfaceOnDefinitionType);

Expand DownExpand Up@@ -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 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
for (int interfaceIndex = 0; interfaceIndex < declTypeRuntimeInterfaces.Length; interfaceIndex++)
{
Expand All@@ -166,6 +176,10 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++)
{
MethodDesc declMethod = virtualSlots[interfaceMethodSlot];

if (!declMethod.Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

if(!interfaceType.IsTypeDefinition)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType);

Expand DownExpand Up@@ -244,9 +258,17 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
// For default interface methods, the generic context is acquired by indexing
// into the interface list of the owning type.
Debug.Assert(providingInterfaceDefinitionType != null);
int indexOfInterface = Array.IndexOf(declTypeDefinitionRuntimeInterfaces, providingInterfaceDefinitionType);
Debug.Assert(indexOfInterface >= 0);
genericContext = StaticVirtualMethodContextSource.ContextFromFirstInterface + indexOfInterface;
if (declTypeDefinition.HasSameTypeDefinition(providingInterfaceDefinitionType) &&
providingInterfaceDefinitionType == declTypeDefinition.InstantiateAsOpen())
{
genericContext = StaticVirtualMethodContextSource.ContextFromThisClass;
}
else
{
int indexOfInterface = Array.IndexOf(declTypeDefinitionRuntimeInterfaces, providingInterfaceDefinitionType);
Debug.Assert(indexOfInterface >= 0);
genericContext = StaticVirtualMethodContextSource.ContextFromFirstInterface + indexOfInterface;
}
}
staticDefaultImplementations.Add((
interfaceIndex,
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,17 +108,21 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)

_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.
// Normal interface don't need one because the dispatch is done at the class level.
// For IDynamicInterfaceCastable, we don't have an implementing class.
if (_type.IsInterface && !((MetadataType)_type).IsDynamicInterfaceCastableImplementation())
return true;
bool isInterface = declType.IsInterface;
bool needsEntriesForInstanceInterfaceMethodImpls = !isInterface
|| ((MetadataType)declType).IsDynamicInterfaceCastableImplementation();

IReadOnlyList<MethodDesc> virtualSlots = factory.VTable(declType).Slots;

for (int i = 0; i < virtualSlots.Count; i++)
{
if (!virtualSlots[i].Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

MethodDesc implMethod = declType.FindVirtualFunctionTargetMethodOnObjectType(virtualSlots[i]);

if (implMethod.CanMethodBeInSealedVTable())
Expand All@@ -143,6 +147,10 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)
for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++)
{
MethodDesc declMethod = virtualSlots[interfaceMethodSlot];

if (!declMethod.Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

if (!interfaceType.IsTypeDefinition)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType);

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,9 +93,8 @@ private static int GetNumberOfSlotsInCurrentType(NodeFactory factory, TypeDesc i
{
if (implType.IsInterface)
{
// We normally don't need to ask about vtable slots of interfaces. It's not wrong to ask
// that question, but we currently only ask it for IDynamicInterfaceCastable implementations.
Debug.Assert(((MetadataType)implType).IsDynamicInterfaceCastableImplementation());
// Interface types don't have physically assigned virtual slots, so the number of slots
// is always 0. They may have sealed slots.
return (implType.HasGenericDictionarySlot() && countDictionarySlots) ? 1 : 0;
}

Expand Down
73 changes: 73 additions & 0 deletions src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,6 +52,8 @@ public static int Run()
TestMoreConstraints.Run();
TestSimpleNonGeneric.Run();
TestSimpleGeneric.Run();
TestDefaultDynamicStaticNonGeneric.Run();
TestDefaultDynamicStaticGeneric.Run();
TestDynamicStaticGenericVirtualMethods.Run();

return Pass;
Expand DownExpand Up@@ -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
{
interface IEntry
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' [release/8.0] Fix runtime dispatch to static virtuals on interface types by github-actions[bot] · Pull Request #91440 · dotnet/runtime · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -614,6 +614,8 @@ private static MethodDesc ResolveInterfaceMethodToVirtualMethodOnType(MethodDesc
{
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)
return null;

Expand DownExpand Up@@ -781,7 +783,7 @@ private static DefaultInterfaceMethodResolution ResolveInterfaceMethodToDefaultI
// If we're asking about an interface, include the interface in the list.
consideredInterfaces = new DefType[currentType.RuntimeInterfaces.Length + 1];
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)
Expand DownExpand Up@@ -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>
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
for (MetadataType typeToCheck = currentType; typeToCheck != null; typeToCheck = typeToCheck.MetadataBaseType)
{
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -372,16 +372,8 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt

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.
needsDependenciesForVirtualMethodImpls &= !factory.VTable(defType).HasFixedSlots;

if (needsDependenciesForVirtualMethodImpls)
if (!factory.VTable(defType).HasFixedSlots)
{
bool isNonInterfaceAbstractType = !defType.IsInterface && ((MetadataType)defType).IsAbstract;

Expand DownExpand Up@@ -436,6 +428,12 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt
((System.Collections.IStructuralEquatable)defType.RuntimeInterfaces).Equals(_type.RuntimeInterfaces,
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
// 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.
Expand All@@ -456,6 +454,9 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt

bool isStaticInterfaceMethod = interfaceMethod.Signature.IsStatic;

if (!isStaticInterfaceMethod && !needsDependenciesForInstanceInterfaceMethodImpls)
continue;

MethodDesc implMethod = isStaticInterfaceMethod ?
defType.ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod) :
defType.ResolveInterfaceMethodToVirtualMethodOnType(interfaceMethod);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,7 +73,7 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact
if (!type.IsArray && !type.IsDefType)
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.
// The only exception are IDynamicInterfaceCastable scenarios that dispatch
// using the interface dispatch map.
Expand All@@ -83,8 +83,9 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact
// 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
// that's reflection and this should work without reflection.
if (type.IsInterface)
return ((MetadataType)type).IsDynamicInterfaceCastableImplementation();
bool isInterface = type.IsInterface;
if (isInterface && ((MetadataType)type).IsDynamicInterfaceCastableImplementation())
return true;

DefType declType = type.GetClosestDefType();

Expand DownExpand Up@@ -112,6 +113,11 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact

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)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), interfaceOnDefinitionType);

Expand DownExpand Up@@ -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 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
for (int interfaceIndex = 0; interfaceIndex < declTypeRuntimeInterfaces.Length; interfaceIndex++)
{
Expand All@@ -166,6 +176,10 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++)
{
MethodDesc declMethod = virtualSlots[interfaceMethodSlot];

if (!declMethod.Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

if(!interfaceType.IsTypeDefinition)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType);

Expand DownExpand Up@@ -244,9 +258,17 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
// For default interface methods, the generic context is acquired by indexing
// into the interface list of the owning type.
Debug.Assert(providingInterfaceDefinitionType != null);
int indexOfInterface = Array.IndexOf(declTypeDefinitionRuntimeInterfaces, providingInterfaceDefinitionType);
Debug.Assert(indexOfInterface >= 0);
genericContext = StaticVirtualMethodContextSource.ContextFromFirstInterface + indexOfInterface;
if (declTypeDefinition.HasSameTypeDefinition(providingInterfaceDefinitionType) &&
providingInterfaceDefinitionType == declTypeDefinition.InstantiateAsOpen())
{
genericContext = StaticVirtualMethodContextSource.ContextFromThisClass;
}
else
{
int indexOfInterface = Array.IndexOf(declTypeDefinitionRuntimeInterfaces, providingInterfaceDefinitionType);
Debug.Assert(indexOfInterface >= 0);
genericContext = StaticVirtualMethodContextSource.ContextFromFirstInterface + indexOfInterface;
}
}
staticDefaultImplementations.Add((
interfaceIndex,
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,17 +108,21 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)

_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.
// Normal interface don't need one because the dispatch is done at the class level.
// For IDynamicInterfaceCastable, we don't have an implementing class.
if (_type.IsInterface && !((MetadataType)_type).IsDynamicInterfaceCastableImplementation())
return true;
bool isInterface = declType.IsInterface;
bool needsEntriesForInstanceInterfaceMethodImpls = !isInterface
|| ((MetadataType)declType).IsDynamicInterfaceCastableImplementation();

IReadOnlyList<MethodDesc> virtualSlots = factory.VTable(declType).Slots;

for (int i = 0; i < virtualSlots.Count; i++)
{
if (!virtualSlots[i].Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

MethodDesc implMethod = declType.FindVirtualFunctionTargetMethodOnObjectType(virtualSlots[i]);

if (implMethod.CanMethodBeInSealedVTable())
Expand All@@ -143,6 +147,10 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)
for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++)
{
MethodDesc declMethod = virtualSlots[interfaceMethodSlot];

if (!declMethod.Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

if (!interfaceType.IsTypeDefinition)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType);

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,9 +93,8 @@ private static int GetNumberOfSlotsInCurrentType(NodeFactory factory, TypeDesc i
{
if (implType.IsInterface)
{
// We normally don't need to ask about vtable slots of interfaces. It's not wrong to ask
// that question, but we currently only ask it for IDynamicInterfaceCastable implementations.
Debug.Assert(((MetadataType)implType).IsDynamicInterfaceCastableImplementation());
// Interface types don't have physically assigned virtual slots, so the number of slots
// is always 0. They may have sealed slots.
return (implType.HasGenericDictionarySlot() && countDictionarySlots) ? 1 : 0;
}

Expand Down
73 changes: 73 additions & 0 deletions src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,6 +52,8 @@ public static int Run()
TestMoreConstraints.Run();
TestSimpleNonGeneric.Run();
TestSimpleGeneric.Run();
TestDefaultDynamicStaticNonGeneric.Run();
TestDefaultDynamicStaticGeneric.Run();
TestDynamicStaticGenericVirtualMethods.Run();

return Pass;
Expand DownExpand Up@@ -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
{
interface IEntry
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [release/8.0] Fix runtime dispatch to static virtuals on interface types by github-actions[bot] · Pull Request #91440 · dotnet/runtime · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -614,6 +614,8 @@ private static MethodDesc ResolveInterfaceMethodToVirtualMethodOnType(MethodDesc
{
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)
return null;

Expand DownExpand Up@@ -781,7 +783,7 @@ private static DefaultInterfaceMethodResolution ResolveInterfaceMethodToDefaultI
// If we're asking about an interface, include the interface in the list.
consideredInterfaces = new DefType[currentType.RuntimeInterfaces.Length + 1];
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)
Expand DownExpand Up@@ -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>
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
for (MetadataType typeToCheck = currentType; typeToCheck != null; typeToCheck = typeToCheck.MetadataBaseType)
{
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -372,16 +372,8 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt

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.
needsDependenciesForVirtualMethodImpls &= !factory.VTable(defType).HasFixedSlots;

if (needsDependenciesForVirtualMethodImpls)
if (!factory.VTable(defType).HasFixedSlots)
{
bool isNonInterfaceAbstractType = !defType.IsInterface && ((MetadataType)defType).IsAbstract;

Expand DownExpand Up@@ -436,6 +428,12 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt
((System.Collections.IStructuralEquatable)defType.RuntimeInterfaces).Equals(_type.RuntimeInterfaces,
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
// 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.
Expand All@@ -456,6 +454,9 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt

bool isStaticInterfaceMethod = interfaceMethod.Signature.IsStatic;

if (!isStaticInterfaceMethod && !needsDependenciesForInstanceInterfaceMethodImpls)
continue;

MethodDesc implMethod = isStaticInterfaceMethod ?
defType.ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod) :
defType.ResolveInterfaceMethodToVirtualMethodOnType(interfaceMethod);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,7 +73,7 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact
if (!type.IsArray && !type.IsDefType)
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.
// The only exception are IDynamicInterfaceCastable scenarios that dispatch
// using the interface dispatch map.
Expand All@@ -83,8 +83,9 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact
// 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
// that's reflection and this should work without reflection.
if (type.IsInterface)
return ((MetadataType)type).IsDynamicInterfaceCastableImplementation();
bool isInterface = type.IsInterface;
if (isInterface && ((MetadataType)type).IsDynamicInterfaceCastableImplementation())
return true;

DefType declType = type.GetClosestDefType();

Expand DownExpand Up@@ -112,6 +113,11 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact

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)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), interfaceOnDefinitionType);

Expand DownExpand Up@@ -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 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
for (int interfaceIndex = 0; interfaceIndex < declTypeRuntimeInterfaces.Length; interfaceIndex++)
{
Expand All@@ -166,6 +176,10 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++)
{
MethodDesc declMethod = virtualSlots[interfaceMethodSlot];

if (!declMethod.Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

if(!interfaceType.IsTypeDefinition)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType);

Expand DownExpand Up@@ -244,9 +258,17 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
// For default interface methods, the generic context is acquired by indexing
// into the interface list of the owning type.
Debug.Assert(providingInterfaceDefinitionType != null);
int indexOfInterface = Array.IndexOf(declTypeDefinitionRuntimeInterfaces, providingInterfaceDefinitionType);
Debug.Assert(indexOfInterface >= 0);
genericContext = StaticVirtualMethodContextSource.ContextFromFirstInterface + indexOfInterface;
if (declTypeDefinition.HasSameTypeDefinition(providingInterfaceDefinitionType) &&
providingInterfaceDefinitionType == declTypeDefinition.InstantiateAsOpen())
{
genericContext = StaticVirtualMethodContextSource.ContextFromThisClass;
}
else
{
int indexOfInterface = Array.IndexOf(declTypeDefinitionRuntimeInterfaces, providingInterfaceDefinitionType);
Debug.Assert(indexOfInterface >= 0);
genericContext = StaticVirtualMethodContextSource.ContextFromFirstInterface + indexOfInterface;
}
}
staticDefaultImplementations.Add((
interfaceIndex,
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,17 +108,21 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)

_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.
// Normal interface don't need one because the dispatch is done at the class level.
// For IDynamicInterfaceCastable, we don't have an implementing class.
if (_type.IsInterface && !((MetadataType)_type).IsDynamicInterfaceCastableImplementation())
return true;
bool isInterface = declType.IsInterface;
bool needsEntriesForInstanceInterfaceMethodImpls = !isInterface
|| ((MetadataType)declType).IsDynamicInterfaceCastableImplementation();

IReadOnlyList<MethodDesc> virtualSlots = factory.VTable(declType).Slots;

for (int i = 0; i < virtualSlots.Count; i++)
{
if (!virtualSlots[i].Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

MethodDesc implMethod = declType.FindVirtualFunctionTargetMethodOnObjectType(virtualSlots[i]);

if (implMethod.CanMethodBeInSealedVTable())
Expand All@@ -143,6 +147,10 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)
for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++)
{
MethodDesc declMethod = virtualSlots[interfaceMethodSlot];

if (!declMethod.Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

if (!interfaceType.IsTypeDefinition)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType);

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,9 +93,8 @@ private static int GetNumberOfSlotsInCurrentType(NodeFactory factory, TypeDesc i
{
if (implType.IsInterface)
{
// We normally don't need to ask about vtable slots of interfaces. It's not wrong to ask
// that question, but we currently only ask it for IDynamicInterfaceCastable implementations.
Debug.Assert(((MetadataType)implType).IsDynamicInterfaceCastableImplementation());
// Interface types don't have physically assigned virtual slots, so the number of slots
// is always 0. They may have sealed slots.
return (implType.HasGenericDictionarySlot() && countDictionarySlots) ? 1 : 0;
}

Expand Down
73 changes: 73 additions & 0 deletions src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,6 +52,8 @@ public static int Run()
TestMoreConstraints.Run();
TestSimpleNonGeneric.Run();
TestSimpleGeneric.Run();
TestDefaultDynamicStaticNonGeneric.Run();
TestDefaultDynamicStaticGeneric.Run();
TestDynamicStaticGenericVirtualMethods.Run();

return Pass;
Expand DownExpand Up@@ -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
{
interface IEntry
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [release/8.0] Fix runtime dispatch to static virtuals on interface types by github-actions[bot] · Pull Request #91440 · dotnet/runtime · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -614,6 +614,8 @@ private static MethodDesc ResolveInterfaceMethodToVirtualMethodOnType(MethodDesc
{
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)
return null;

Expand DownExpand Up@@ -781,7 +783,7 @@ private static DefaultInterfaceMethodResolution ResolveInterfaceMethodToDefaultI
// If we're asking about an interface, include the interface in the list.
consideredInterfaces = new DefType[currentType.RuntimeInterfaces.Length + 1];
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)
Expand DownExpand Up@@ -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>
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
for (MetadataType typeToCheck = currentType; typeToCheck != null; typeToCheck = typeToCheck.MetadataBaseType)
{
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -372,16 +372,8 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt

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.
needsDependenciesForVirtualMethodImpls &= !factory.VTable(defType).HasFixedSlots;

if (needsDependenciesForVirtualMethodImpls)
if (!factory.VTable(defType).HasFixedSlots)
{
bool isNonInterfaceAbstractType = !defType.IsInterface && ((MetadataType)defType).IsAbstract;

Expand DownExpand Up@@ -436,6 +428,12 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt
((System.Collections.IStructuralEquatable)defType.RuntimeInterfaces).Equals(_type.RuntimeInterfaces,
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
// 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.
Expand All@@ -456,6 +454,9 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt

bool isStaticInterfaceMethod = interfaceMethod.Signature.IsStatic;

if (!isStaticInterfaceMethod && !needsDependenciesForInstanceInterfaceMethodImpls)
continue;

MethodDesc implMethod = isStaticInterfaceMethod ?
defType.ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod) :
defType.ResolveInterfaceMethodToVirtualMethodOnType(interfaceMethod);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,7 +73,7 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact
if (!type.IsArray && !type.IsDefType)
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.
// The only exception are IDynamicInterfaceCastable scenarios that dispatch
// using the interface dispatch map.
Expand All@@ -83,8 +83,9 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact
// 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
// that's reflection and this should work without reflection.
if (type.IsInterface)
return ((MetadataType)type).IsDynamicInterfaceCastableImplementation();
bool isInterface = type.IsInterface;
if (isInterface && ((MetadataType)type).IsDynamicInterfaceCastableImplementation())
return true;

DefType declType = type.GetClosestDefType();

Expand DownExpand Up@@ -112,6 +113,11 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact

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)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), interfaceOnDefinitionType);

Expand DownExpand Up@@ -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 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
for (int interfaceIndex = 0; interfaceIndex < declTypeRuntimeInterfaces.Length; interfaceIndex++)
{
Expand All@@ -166,6 +176,10 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++)
{
MethodDesc declMethod = virtualSlots[interfaceMethodSlot];

if (!declMethod.Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

if(!interfaceType.IsTypeDefinition)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType);

Expand DownExpand Up@@ -244,9 +258,17 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
// For default interface methods, the generic context is acquired by indexing
// into the interface list of the owning type.
Debug.Assert(providingInterfaceDefinitionType != null);
int indexOfInterface = Array.IndexOf(declTypeDefinitionRuntimeInterfaces, providingInterfaceDefinitionType);
Debug.Assert(indexOfInterface >= 0);
genericContext = StaticVirtualMethodContextSource.ContextFromFirstInterface + indexOfInterface;
if (declTypeDefinition.HasSameTypeDefinition(providingInterfaceDefinitionType) &&
providingInterfaceDefinitionType == declTypeDefinition.InstantiateAsOpen())
{
genericContext = StaticVirtualMethodContextSource.ContextFromThisClass;
}
else
{
int indexOfInterface = Array.IndexOf(declTypeDefinitionRuntimeInterfaces, providingInterfaceDefinitionType);
Debug.Assert(indexOfInterface >= 0);
genericContext = StaticVirtualMethodContextSource.ContextFromFirstInterface + indexOfInterface;
}
}
staticDefaultImplementations.Add((
interfaceIndex,
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,17 +108,21 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)

_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.
// Normal interface don't need one because the dispatch is done at the class level.
// For IDynamicInterfaceCastable, we don't have an implementing class.
if (_type.IsInterface && !((MetadataType)_type).IsDynamicInterfaceCastableImplementation())
return true;
bool isInterface = declType.IsInterface;
bool needsEntriesForInstanceInterfaceMethodImpls = !isInterface
|| ((MetadataType)declType).IsDynamicInterfaceCastableImplementation();

IReadOnlyList<MethodDesc> virtualSlots = factory.VTable(declType).Slots;

for (int i = 0; i < virtualSlots.Count; i++)
{
if (!virtualSlots[i].Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

MethodDesc implMethod = declType.FindVirtualFunctionTargetMethodOnObjectType(virtualSlots[i]);

if (implMethod.CanMethodBeInSealedVTable())
Expand All@@ -143,6 +147,10 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)
for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++)
{
MethodDesc declMethod = virtualSlots[interfaceMethodSlot];

if (!declMethod.Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

if (!interfaceType.IsTypeDefinition)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType);

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,9 +93,8 @@ private static int GetNumberOfSlotsInCurrentType(NodeFactory factory, TypeDesc i
{
if (implType.IsInterface)
{
// We normally don't need to ask about vtable slots of interfaces. It's not wrong to ask
// that question, but we currently only ask it for IDynamicInterfaceCastable implementations.
Debug.Assert(((MetadataType)implType).IsDynamicInterfaceCastableImplementation());
// Interface types don't have physically assigned virtual slots, so the number of slots
// is always 0. They may have sealed slots.
return (implType.HasGenericDictionarySlot() && countDictionarySlots) ? 1 : 0;
}

Expand Down
73 changes: 73 additions & 0 deletions src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,6 +52,8 @@ public static int Run()
TestMoreConstraints.Run();
TestSimpleNonGeneric.Run();
TestSimpleGeneric.Run();
TestDefaultDynamicStaticNonGeneric.Run();
TestDefaultDynamicStaticGeneric.Run();
TestDynamicStaticGenericVirtualMethods.Run();

return Pass;
Expand DownExpand Up@@ -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
{
interface IEntry
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); [release/8.0] Fix runtime dispatch to static virtuals on interface types by github-actions[bot] · Pull Request #91440 · dotnet/runtime · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -614,6 +614,8 @@ private static MethodDesc ResolveInterfaceMethodToVirtualMethodOnType(MethodDesc
{
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)
return null;

Expand DownExpand Up@@ -781,7 +783,7 @@ private static DefaultInterfaceMethodResolution ResolveInterfaceMethodToDefaultI
// If we're asking about an interface, include the interface in the list.
consideredInterfaces = new DefType[currentType.RuntimeInterfaces.Length + 1];
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)
Expand DownExpand Up@@ -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>
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
for (MetadataType typeToCheck = currentType; typeToCheck != null; typeToCheck = typeToCheck.MetadataBaseType)
{
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -372,16 +372,8 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt

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.
needsDependenciesForVirtualMethodImpls &= !factory.VTable(defType).HasFixedSlots;

if (needsDependenciesForVirtualMethodImpls)
if (!factory.VTable(defType).HasFixedSlots)
{
bool isNonInterfaceAbstractType = !defType.IsInterface && ((MetadataType)defType).IsAbstract;

Expand DownExpand Up@@ -436,6 +428,12 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt
((System.Collections.IStructuralEquatable)defType.RuntimeInterfaces).Equals(_type.RuntimeInterfaces,
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
// 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.
Expand All@@ -456,6 +454,9 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt

bool isStaticInterfaceMethod = interfaceMethod.Signature.IsStatic;

if (!isStaticInterfaceMethod && !needsDependenciesForInstanceInterfaceMethodImpls)
continue;

MethodDesc implMethod = isStaticInterfaceMethod ?
defType.ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceMethod) :
defType.ResolveInterfaceMethodToVirtualMethodOnType(interfaceMethod);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,7 +73,7 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact
if (!type.IsArray && !type.IsDefType)
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.
// The only exception are IDynamicInterfaceCastable scenarios that dispatch
// using the interface dispatch map.
Expand All@@ -83,8 +83,9 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact
// 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
// that's reflection and this should work without reflection.
if (type.IsInterface)
return ((MetadataType)type).IsDynamicInterfaceCastableImplementation();
bool isInterface = type.IsInterface;
if (isInterface && ((MetadataType)type).IsDynamicInterfaceCastableImplementation())
return true;

DefType declType = type.GetClosestDefType();

Expand DownExpand Up@@ -112,6 +113,11 @@ public static bool MightHaveInterfaceDispatchMap(TypeDesc type, NodeFactory fact

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)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), interfaceOnDefinitionType);

Expand DownExpand Up@@ -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 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
for (int interfaceIndex = 0; interfaceIndex < declTypeRuntimeInterfaces.Length; interfaceIndex++)
{
Expand All@@ -166,6 +176,10 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++)
{
MethodDesc declMethod = virtualSlots[interfaceMethodSlot];

if (!declMethod.Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

if(!interfaceType.IsTypeDefinition)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType);

Expand DownExpand Up@@ -244,9 +258,17 @@ private void EmitDispatchMap(ref ObjectDataBuilder builder, NodeFactory factory)
// For default interface methods, the generic context is acquired by indexing
// into the interface list of the owning type.
Debug.Assert(providingInterfaceDefinitionType != null);
int indexOfInterface = Array.IndexOf(declTypeDefinitionRuntimeInterfaces, providingInterfaceDefinitionType);
Debug.Assert(indexOfInterface >= 0);
genericContext = StaticVirtualMethodContextSource.ContextFromFirstInterface + indexOfInterface;
if (declTypeDefinition.HasSameTypeDefinition(providingInterfaceDefinitionType) &&
providingInterfaceDefinitionType == declTypeDefinition.InstantiateAsOpen())
{
genericContext = StaticVirtualMethodContextSource.ContextFromThisClass;
}
else
{
int indexOfInterface = Array.IndexOf(declTypeDefinitionRuntimeInterfaces, providingInterfaceDefinitionType);
Debug.Assert(indexOfInterface >= 0);
genericContext = StaticVirtualMethodContextSource.ContextFromFirstInterface + indexOfInterface;
}
}
staticDefaultImplementations.Add((
interfaceIndex,
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,17 +108,21 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)

_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.
// Normal interface don't need one because the dispatch is done at the class level.
// For IDynamicInterfaceCastable, we don't have an implementing class.
if (_type.IsInterface && !((MetadataType)_type).IsDynamicInterfaceCastableImplementation())
return true;
bool isInterface = declType.IsInterface;
bool needsEntriesForInstanceInterfaceMethodImpls = !isInterface
|| ((MetadataType)declType).IsDynamicInterfaceCastableImplementation();

IReadOnlyList<MethodDesc> virtualSlots = factory.VTable(declType).Slots;

for (int i = 0; i < virtualSlots.Count; i++)
{
if (!virtualSlots[i].Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

MethodDesc implMethod = declType.FindVirtualFunctionTargetMethodOnObjectType(virtualSlots[i]);

if (implMethod.CanMethodBeInSealedVTable())
Expand All@@ -143,6 +147,10 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)
for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++)
{
MethodDesc declMethod = virtualSlots[interfaceMethodSlot];

if (!declMethod.Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
continue;

if (!interfaceType.IsTypeDefinition)
declMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)interfaceDefinitionType);

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,9 +93,8 @@ private static int GetNumberOfSlotsInCurrentType(NodeFactory factory, TypeDesc i
{
if (implType.IsInterface)
{
// We normally don't need to ask about vtable slots of interfaces. It's not wrong to ask
// that question, but we currently only ask it for IDynamicInterfaceCastable implementations.
Debug.Assert(((MetadataType)implType).IsDynamicInterfaceCastableImplementation());
// Interface types don't have physically assigned virtual slots, so the number of slots
// is always 0. They may have sealed slots.
return (implType.HasGenericDictionarySlot() && countDictionarySlots) ? 1 : 0;
}

Expand Down
73 changes: 73 additions & 0 deletions src/tests/nativeaot/SmokeTests/UnitTests/Interfaces.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,6 +52,8 @@ public static int Run()
TestMoreConstraints.Run();
TestSimpleNonGeneric.Run();
TestSimpleGeneric.Run();
TestDefaultDynamicStaticNonGeneric.Run();
TestDefaultDynamicStaticGeneric.Run();
TestDynamicStaticGenericVirtualMethods.Run();

return Pass;
Expand DownExpand Up@@ -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
{
interface IEntry
Expand Down