Skip to content

Commit

Permalink
Type loader support for static virtual methods
Browse files Browse the repository at this point in the history
Fixes dotnet#67745.

Support for static virtual methods that was added in dotnet#66084 was enough for compile-time resolution of static virtual methods, but didn't cover dynamic code. For example, given following code:

```csharp
interface IFoo { static virtual void Frob(); }
class SomeCaller<T> where T : IFoo { ... T.Frob(); }
class SomeClass : IFoo { ... }
```

If we do `typeof(SomeCaller<>).MakeGenericType(typeof(SomeClass)` at runtime, the runtime has to find what method implements `IFoo.Frob` on `SomeClass` and ensure proper data structures are generated for `SomeCaller<SomeClass>` so that the call lands in the right spot at runtime.

On a high level, what we need:
* Change to the compiler to generate extra data ("interface dispatch maps") that lets us find an implementation of interface method X on a given type Y.
* Change to the runtime to read the new data structure.
* Change to the compiler to generate extra method bodies for types that can potentially be used with MakeGeneric at runtime. This is an overapproximation since we don't know the set of types that will really be used.
* Change to type loader data structures to capture when shared generic code needs to do this mapping, and change the code in the compiler that emits it, and to the type loader that reads it.

I've made it so that the dispatch logic between instance and static methods is shared. It's not strictly necessary for both to go into the same data structure, but it prevents duplicating the code on the emission and reading side. The side effect of that is that static virtual methods now go into the sealed vtable. We have to put them somewhere. This spot is as good as any.

I've also had to make a small change to the ordering of data structure generation within the type loader. I've made is so that EEType/MethodTable structures are fully populated before we start filling out generic dictionaries. This prevents us from calling into the runtime dispatch logic with EETypes/MethodTables that are not actually built yet. I really didn't want to duplicate the dispatch logic into the type loader.
  • Loading branch information
MichalStrehovsky committed Jun 27, 2022
1 parent 05ae801 commit 0194065
Show file tree
Hide file tree
Showing 20 changed files with 596 additions and 67 deletions.
51 changes: 47 additions & 4 deletions src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,18 @@ internal unsafe struct DispatchMapEntry
internal ushort _usInterfaceMethodSlot;
internal ushort _usImplMethodSlot;
}
[StructLayout(LayoutKind.Sequential)]
internal struct StaticDispatchMapEntry
{
// Do not put any other fields before this one. We need StaticDispatchMapEntry* be castable to DispatchMapEntry*.
internal DispatchMapEntry _entry;
internal ushort _usContextMapSource;
}

private ushort _standardEntryCount; // Implementations on the class
private ushort _defaultEntryCount; // Default implementations
private ushort _standardStaticEntryCount; // Implementations on the class (static virtuals)
private ushort _defaultStaticEntryCount; // Default implementations (static virtuals)
private DispatchMapEntry _dispatchMap; // at least one entry if any interfaces defined

public uint NumStandardEntries
Expand Down Expand Up @@ -101,20 +110,54 @@ public uint NumDefaultEntries
#endif
}

public int Size
public uint NumStandardStaticEntries
{
get
{
return sizeof(ushort) + sizeof(ushort) + sizeof(DispatchMapEntry) * ((int)_standardEntryCount + (int)_defaultEntryCount);
return _standardStaticEntryCount;
}
#if TYPE_LOADER_IMPLEMENTATION
set
{
_standardStaticEntryCount = checked((ushort)value);
}
#endif
}

public DispatchMapEntry* this[int index]
public uint NumDefaultStaticEntries
{
get
{
return (DispatchMapEntry*)Unsafe.AsPointer(ref Unsafe.Add(ref _dispatchMap, index));
return _defaultStaticEntryCount;
}
#if TYPE_LOADER_IMPLEMENTATION
set
{
_defaultStaticEntryCount = checked((ushort)value);
}
#endif
}

public int Size
{
get
{
return sizeof(ushort) + sizeof(ushort) + sizeof(ushort) + sizeof(ushort)
+ sizeof(DispatchMapEntry) * ((int)_standardEntryCount + (int)_defaultEntryCount)
+ sizeof(StaticDispatchMapEntry) * ((int)_standardStaticEntryCount + (int)_defaultStaticEntryCount);
}
}

public DispatchMapEntry* GetEntry(int index)
{
Debug.Assert(index <= _defaultEntryCount + _standardEntryCount);
return (DispatchMapEntry*)Unsafe.AsPointer(ref Unsafe.Add(ref _dispatchMap, index));
}

public DispatchMapEntry* GetStaticEntry(int index)
{
Debug.Assert(index <= _defaultStaticEntryCount + _standardStaticEntryCount);
return (DispatchMapEntry*)(((StaticDispatchMapEntry*)Unsafe.AsPointer(ref Unsafe.Add(ref _dispatchMap, _standardEntryCount + _defaultEntryCount))) + index);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ private static IntPtr RhResolveDispatch(object pObject, EETypePtr interfaceType,
}

[RuntimeExport("RhResolveDispatchOnType")]
private static IntPtr RhResolveDispatchOnType(EETypePtr instanceType, EETypePtr interfaceType, ushort slot)
private static IntPtr RhResolveDispatchOnType(EETypePtr instanceType, EETypePtr interfaceType, ushort slot, EETypePtr* pGenericContext)
{
// Type of object we're dispatching on.
MethodTable* pInstanceType = instanceType.ToPointer();
Expand All @@ -92,7 +92,8 @@ private static IntPtr RhResolveDispatchOnType(EETypePtr instanceType, EETypePtr

return DispatchResolve.FindInterfaceMethodImplementationTarget(pInstanceType,
pInterfaceType,
slot);
slot,
(MethodTable**)pGenericContext);
}

private static unsafe IntPtr RhResolveDispatchWorker(object pObject, void* cell, ref DispatchCellInfo cellInfo)
Expand All @@ -108,7 +109,8 @@ private static unsafe IntPtr RhResolveDispatchWorker(object pObject, void* cell,

IntPtr pTargetCode = DispatchResolve.FindInterfaceMethodImplementationTarget(pResolvingInstanceType,
cellInfo.InterfaceType.ToPointer(),
cellInfo.InterfaceSlot);
cellInfo.InterfaceSlot,
ppGenericContext: null);
if (pTargetCode == IntPtr.Zero && pInstanceType->IsIDynamicInterfaceCastable)
{
// Dispatch not resolved through normal dispatch map, try using the IDynamicInterfaceCastable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ internal static unsafe class DispatchResolve
{
public static IntPtr FindInterfaceMethodImplementationTarget(MethodTable* pTgtType,
MethodTable* pItfType,
ushort itfSlotNumber)
ushort itfSlotNumber,
/* out */ MethodTable** ppGenericContext)
{
DynamicModule* dynamicModule = pTgtType->DynamicModule;

Expand All @@ -39,7 +40,7 @@ public static IntPtr FindInterfaceMethodImplementationTarget(MethodTable* pTgtTy
{
ushort implSlotNumber;
if (FindImplSlotForCurrentType(
pCur, pItfType, itfSlotNumber, fDoDefaultImplementationLookup, &implSlotNumber))
pCur, pItfType, itfSlotNumber, fDoDefaultImplementationLookup, &implSlotNumber, ppGenericContext))
{
IntPtr targetMethod;
if (implSlotNumber < pCur->NumVtableSlots)
Expand Down Expand Up @@ -85,7 +86,8 @@ private static bool FindImplSlotForCurrentType(MethodTable* pTgtType,
MethodTable* pItfType,
ushort itfSlotNumber,
bool fDoDefaultImplementationLookup,
ushort* pImplSlotNumber)
ushort* pImplSlotNumber,
MethodTable** ppGenericContext)
{
bool fRes = false;

Expand All @@ -110,13 +112,13 @@ private static bool FindImplSlotForCurrentType(MethodTable* pTgtType,
bool fDoVariantLookup = false; // do not check variance for first scan of dispatch map

fRes = FindImplSlotInSimpleMap(
pTgtType, pItfType, itfSlotNumber, pImplSlotNumber, fDoVariantLookup, fDoDefaultImplementationLookup);
pTgtType, pItfType, itfSlotNumber, pImplSlotNumber, ppGenericContext, fDoVariantLookup, fDoDefaultImplementationLookup);

if (!fRes)
{
fDoVariantLookup = true; // check variance for second scan of dispatch map
fRes = FindImplSlotInSimpleMap(
pTgtType, pItfType, itfSlotNumber, pImplSlotNumber, fDoVariantLookup, fDoDefaultImplementationLookup);
pTgtType, pItfType, itfSlotNumber, pImplSlotNumber, ppGenericContext, fDoVariantLookup, fDoDefaultImplementationLookup);
}
}

Expand All @@ -127,6 +129,7 @@ private static bool FindImplSlotInSimpleMap(MethodTable* pTgtType,
MethodTable* pItfType,
uint itfSlotNumber,
ushort* pImplSlotNumber,
MethodTable** ppGenericContext,
bool actuallyCheckVariance,
bool checkDefaultImplementations)
{
Expand Down Expand Up @@ -176,10 +179,18 @@ private static bool FindImplSlotInSimpleMap(MethodTable* pTgtType,
}
}

// It only makes sense to ask for generic context if we're asking about a static method
bool fStaticDispatch = ppGenericContext != null;

// We either scan the instance or static portion of the dispatch map. Depends on what the caller wants.
DispatchMap* pMap = pTgtType->DispatchMap;
DispatchMap.DispatchMapEntry* i = (*pMap)[checkDefaultImplementations ? (int)pMap->NumStandardEntries : 0];
DispatchMap.DispatchMapEntry* iEnd = (*pMap)[checkDefaultImplementations ? (int)(pMap->NumStandardEntries + pMap->NumDefaultEntries) : (int)pMap->NumStandardEntries];
for (; i != iEnd; ++i)
DispatchMap.DispatchMapEntry* i = fStaticDispatch ?
pMap->GetStaticEntry(checkDefaultImplementations ? (int)pMap->NumStandardStaticEntries : 0) :
pMap->GetEntry(checkDefaultImplementations ? (int)pMap->NumStandardEntries : 0);
DispatchMap.DispatchMapEntry* iEnd = fStaticDispatch ?
pMap->GetStaticEntry(checkDefaultImplementations ? (int)(pMap->NumStandardStaticEntries + pMap->NumDefaultStaticEntries) : (int)pMap->NumStandardStaticEntries) :
pMap->GetEntry(checkDefaultImplementations ? (int)(pMap->NumStandardEntries + pMap->NumDefaultEntries) : (int)pMap->NumStandardEntries);
for (; i != iEnd; i = fStaticDispatch ? (DispatchMap.DispatchMapEntry*)(((DispatchMap.StaticDispatchMapEntry*)i) + 1) : i + 1)
{
if (i->_usInterfaceMethodSlot == itfSlotNumber)
{
Expand All @@ -192,6 +203,12 @@ private static bool FindImplSlotInSimpleMap(MethodTable* pTgtType,
if (pCurEntryType == pItfType)
{
*pImplSlotNumber = i->_usImplMethodSlot;

// If this is a static method, the entry point is not usable without generic context.
// (Instance methods acquire the generic context from their `this`.)
if (fStaticDispatch)
*ppGenericContext = GetGenericContextSource(pTgtType, i);

return true;
}
else if (fCheckVariance && ((fArrayCovariance && pCurEntryType->IsGeneric) || pCurEntryType->HasGenericVariance))
Expand Down Expand Up @@ -227,6 +244,12 @@ private static bool FindImplSlotInSimpleMap(MethodTable* pTgtType,
if (TypeCast.TypeParametersAreCompatible(itfArity, pCurEntryInstantiation, pItfInstantiation, pItfVarianceInfo, fArrayCovariance, null))
{
*pImplSlotNumber = i->_usImplMethodSlot;

// If this is a static method, the entry point is not usable without generic context.
// (Instance methods acquire the generic context from their `this`.)
if (fStaticDispatch)
*ppGenericContext = GetGenericContextSource(pTgtType, i);

return true;
}
}
Expand All @@ -235,5 +258,16 @@ private static bool FindImplSlotInSimpleMap(MethodTable* pTgtType,

return false;
}

private static unsafe MethodTable* GetGenericContextSource(MethodTable* pTgtType, DispatchMap.DispatchMapEntry* pEntry)
{
ushort usEncodedValue = ((DispatchMap.StaticDispatchMapEntry*)pEntry)->_usContextMapSource;
return usEncodedValue switch
{
StaticVirtualMethodContextSource.None => null,
StaticVirtualMethodContextSource.ContextFromThisClass => pTgtType,
_ => pTgtType->InterfaceMap[usEncodedValue - StaticVirtualMethodContextSource.ContextFromFirstInterface].InterfaceType
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,17 @@ public static IntPtr ResolveDispatchOnType(RuntimeTypeHandle instanceType, Runti
return RuntimeImports.RhResolveDispatchOnType(CreateEETypePtr(instanceType), CreateEETypePtr(interfaceType), checked((ushort)slot));
}

public static unsafe IntPtr ResolveStaticDispatchOnType(RuntimeTypeHandle instanceType, RuntimeTypeHandle interfaceType, int slot, out RuntimeTypeHandle genericContext)
{
EETypePtr genericContextPtr = default;
IntPtr result = RuntimeImports.RhResolveDispatchOnType(CreateEETypePtr(instanceType), CreateEETypePtr(interfaceType), checked((ushort)slot), &genericContextPtr);
if (result != IntPtr.Zero)
genericContext = new RuntimeTypeHandle(genericContextPtr);
else
genericContext = default;
return result;
}

public static IntPtr ResolveDispatch(object instance, RuntimeTypeHandle interfaceType, int slot)
{
return RuntimeImports.RhResolveDispatch(instance, CreateEETypePtr(interfaceType), checked((ushort)slot));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,14 @@ internal static unsafe int RhCompatibleReentrantWaitAny(bool alertable, int time

[MethodImplAttribute(MethodImplOptions.InternalCall)]
[RuntimeImport(RuntimeLibrary, "RhResolveDispatchOnType")]
internal static extern IntPtr RhResolveDispatchOnType(EETypePtr instanceType, EETypePtr interfaceType, ushort slot);
// For my life cannot figure out the ordering of modifiers this is expecting.
#pragma warning disable IDE0036
internal static extern unsafe IntPtr RhResolveDispatchOnType(EETypePtr instanceType, EETypePtr interfaceType, ushort slot, EETypePtr* pGenericContext);

internal static unsafe IntPtr RhResolveDispatchOnType(EETypePtr instanceType, EETypePtr interfaceType, ushort slot)
{
return RhResolveDispatchOnType(instanceType, interfaceType, slot, null);
}

[MethodImplAttribute(MethodImplOptions.InternalCall)]
[RuntimeImport(RuntimeLibrary, "RhGetRuntimeHelperForType")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -583,14 +583,25 @@ private static void CreateEETypeWorker(MethodTable* pTemplateEEType, uint hashCo
DispatchMap* pDynamicDispatchMap = (DispatchMap*)dynamicDispatchMapPtr;
pDynamicDispatchMap->NumStandardEntries = pTemplateDispatchMap->NumStandardEntries;
pDynamicDispatchMap->NumDefaultEntries = pTemplateDispatchMap->NumDefaultEntries;
pDynamicDispatchMap->NumStandardStaticEntries = pTemplateDispatchMap->NumStandardStaticEntries;
pDynamicDispatchMap->NumDefaultStaticEntries = pTemplateDispatchMap->NumDefaultStaticEntries;

for (int i = 0; i < pTemplateDispatchMap->NumStandardEntries + pTemplateDispatchMap->NumDefaultEntries; i++)
uint numInstanceEntries = pTemplateDispatchMap->NumStandardEntries + pTemplateDispatchMap->NumDefaultEntries;
for (uint i = 0; i < numInstanceEntries + pTemplateDispatchMap->NumStandardStaticEntries + pTemplateDispatchMap->NumDefaultStaticEntries; i++)
{
DispatchMap.DispatchMapEntry* pTemplateEntry = (*pTemplateDispatchMap)[i];
DispatchMap.DispatchMapEntry* pDynamicEntry = (*pDynamicDispatchMap)[i];
DispatchMap.DispatchMapEntry* pTemplateEntry = i < numInstanceEntries ?
pTemplateDispatchMap->GetEntry((int)i) :
pTemplateDispatchMap->GetStaticEntry((int)(i - numInstanceEntries));
DispatchMap.DispatchMapEntry* pDynamicEntry = i < numInstanceEntries ?
pDynamicDispatchMap->GetEntry((int)i) :
pDynamicDispatchMap->GetStaticEntry((int)(i - numInstanceEntries));

pDynamicEntry->_usInterfaceIndex = pTemplateEntry->_usInterfaceIndex;
pDynamicEntry->_usInterfaceMethodSlot = pTemplateEntry->_usInterfaceMethodSlot;
if (i >= numInstanceEntries)
{
((DispatchMap.StaticDispatchMapEntry*)pDynamicEntry)->_usContextMapSource = ((DispatchMap.StaticDispatchMapEntry*)pTemplateEntry)->_usContextMapSource;
}
if (pTemplateEntry->_usImplMethodSlot < pTemplateEEType->NumVtableSlots)
{
pDynamicEntry->_usImplMethodSlot = (ushort)state.VTableSlotsMapping.GetVTableSlotInTargetType(pTemplateEntry->_usImplMethodSlot);
Expand Down
Loading

0 comments on commit 0194065

Please sign in to comment.