Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[release/9.0-staging] Fix IDynamicInterfaceCastable with shared generic code #109918

Merged
Merged
Show file tree
Hide file tree
Changes from 2 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 number Diff line number Diff line change
Expand Up @@ -82,28 +82,57 @@ private static IntPtr RhResolveDispatch(object pObject, MethodTable* interfaceTy
}

[RuntimeExport("RhResolveDispatchOnType")]
private static IntPtr RhResolveDispatchOnType(MethodTable* pInstanceType, MethodTable* pInterfaceType, ushort slot, MethodTable** ppGenericContext)
private static IntPtr RhResolveDispatchOnType(MethodTable* pInstanceType, MethodTable* pInterfaceType, ushort slot)
{
return DispatchResolve.FindInterfaceMethodImplementationTarget(pInstanceType,
pInterfaceType,
slot,
flags: default,
ppGenericContext: null);
}

[RuntimeExport("RhResolveStaticDispatchOnType")]
private static IntPtr RhResolveStaticDispatchOnType(MethodTable* pInstanceType, MethodTable* pInterfaceType, ushort slot, MethodTable** ppGenericContext)
{
return DispatchResolve.FindInterfaceMethodImplementationTarget(pInstanceType,
pInterfaceType,
slot,
DispatchResolve.ResolveFlags.Static,
ppGenericContext);
}

[RuntimeExport("RhResolveDynamicInterfaceCastableDispatchOnType")]
private static IntPtr RhResolveDynamicInterfaceCastableDispatchOnType(MethodTable* pInstanceType, MethodTable* pInterfaceType, ushort slot, MethodTable** ppGenericContext)
{
IntPtr result = DispatchResolve.FindInterfaceMethodImplementationTarget(pInstanceType,
pInterfaceType,
slot,
DispatchResolve.ResolveFlags.IDynamicInterfaceCastable,
ppGenericContext);

if ((result & (nint)DispatchMapCodePointerFlags.RequiresInstantiatingThunkFlag) != 0)
{
result &= ~(nint)DispatchMapCodePointerFlags.RequiresInstantiatingThunkFlag;
}
else
{
*ppGenericContext = null;
}

return result;
}

private static unsafe IntPtr RhResolveDispatchWorker(object pObject, void* cell, ref DispatchCellInfo cellInfo)
{
// Type of object we're dispatching on.
MethodTable* pInstanceType = pObject.GetMethodTable();

if (cellInfo.CellType == DispatchCellType.InterfaceAndSlot)
{
// Type whose DispatchMap is used. Usually the same as the above but for types which implement IDynamicInterfaceCastable
// we may repeat this process with an alternate type.
MethodTable* pResolvingInstanceType = pInstanceType;

IntPtr pTargetCode = DispatchResolve.FindInterfaceMethodImplementationTarget(pResolvingInstanceType,
IntPtr pTargetCode = DispatchResolve.FindInterfaceMethodImplementationTarget(pInstanceType,
cellInfo.InterfaceType,
cellInfo.InterfaceSlot,
flags: default,
ppGenericContext: null);
if (pTargetCode == IntPtr.Zero && pInstanceType->IsIDynamicInterfaceCastable)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ internal static unsafe class DispatchResolve
public static IntPtr FindInterfaceMethodImplementationTarget(MethodTable* pTgtType,
MethodTable* pItfType,
ushort itfSlotNumber,
ResolveFlags flags,
/* out */ MethodTable** ppGenericContext)
{
// We set this bit below during second pass, callers should not set it.
Debug.Assert((flags & ResolveFlags.DefaultInterfaceImplementation) == 0);

// Start at the current type and work up the inheritance chain
MethodTable* pCur = pTgtType;

// We first look at non-default implementation. Default implementations are only considered
// if the "old algorithm" didn't come up with an answer.
bool fDoDefaultImplementationLookup = false;

again:
while (pCur != null)
{
ushort implSlotNumber;
if (FindImplSlotForCurrentType(
pCur, pItfType, itfSlotNumber, fDoDefaultImplementationLookup, &implSlotNumber, ppGenericContext))
pCur, pItfType, itfSlotNumber, flags, &implSlotNumber, ppGenericContext))
{
IntPtr targetMethod;
if (implSlotNumber < pCur->NumVtableSlots)
Expand Down Expand Up @@ -58,9 +58,9 @@ public static IntPtr FindInterfaceMethodImplementationTarget(MethodTable* pTgtTy
}

// If we haven't found an implementation, do a second pass looking for a default implementation.
if (!fDoDefaultImplementationLookup)
if ((flags & ResolveFlags.DefaultInterfaceImplementation) == 0)
{
fDoDefaultImplementationLookup = true;
flags |= ResolveFlags.DefaultInterfaceImplementation;
pCur = pTgtType;
goto again;
}
Expand All @@ -72,10 +72,13 @@ public static IntPtr FindInterfaceMethodImplementationTarget(MethodTable* pTgtTy
private static bool FindImplSlotForCurrentType(MethodTable* pTgtType,
MethodTable* pItfType,
ushort itfSlotNumber,
bool fDoDefaultImplementationLookup,
ResolveFlags flags,
ushort* pImplSlotNumber,
MethodTable** ppGenericContext)
{
// We set this below during second pass, callers should not set this.
Debug.Assert((flags & ResolveFlags.Variant) == 0);

bool fRes = false;

// If making a call and doing virtual resolution don't look into the dispatch map,
Expand All @@ -96,16 +99,14 @@ private static bool FindImplSlotForCurrentType(MethodTable* pTgtType,
// result in interesting behavior such as a derived type only overriding one particular instantiation
// and funneling all the dispatches to it, but its the algorithm.

bool fDoVariantLookup = false; // do not check variance for first scan of dispatch map

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

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

Expand All @@ -117,8 +118,7 @@ private static bool FindImplSlotInSimpleMap(MethodTable* pTgtType,
uint itfSlotNumber,
ushort* pImplSlotNumber,
MethodTable** ppGenericContext,
bool actuallyCheckVariance,
bool checkDefaultImplementations)
ResolveFlags flags)
{
Debug.Assert(pTgtType->HasDispatchMap, "Missing dispatch map");

Expand All @@ -130,7 +130,7 @@ private static bool FindImplSlotInSimpleMap(MethodTable* pTgtType,
bool fCheckVariance = false;
bool fArrayCovariance = false;

if (actuallyCheckVariance)
if ((flags & ResolveFlags.Variant) != 0)
{
fCheckVariance = pItfType->HasGenericVariance;
fArrayCovariance = pTgtType->IsArray;
Expand Down Expand Up @@ -166,8 +166,8 @@ 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;
bool fStaticDispatch = (flags & ResolveFlags.Static) != 0;
bool checkDefaultImplementations = (flags & ResolveFlags.DefaultInterfaceImplementation) != 0;

// We either scan the instance or static portion of the dispatch map. Depends on what the caller wants.
DispatchMap* pMap = pTgtType->DispatchMap;
Expand All @@ -190,8 +190,11 @@ private static bool FindImplSlotInSimpleMap(MethodTable* pTgtType,

// If this is a static method, the entry point is not usable without generic context.
// (Instance methods acquire the generic context from their `this`.)
// Same for IDynamicInterfaceCastable (that has a `this` but it's not useful)
if (fStaticDispatch)
*ppGenericContext = GetGenericContextSource(pTgtType, i);
else if ((flags & ResolveFlags.IDynamicInterfaceCastable) != 0)
*ppGenericContext = pTgtType;

return true;
}
Expand Down Expand Up @@ -231,8 +234,11 @@ private static bool FindImplSlotInSimpleMap(MethodTable* pTgtType,

// If this is a static method, the entry point is not usable without generic context.
// (Instance methods acquire the generic context from their `this`.)
// Same for IDynamicInterfaceCastable (that has a `this` but it's not useful)
if (fStaticDispatch)
*ppGenericContext = GetGenericContextSource(pTgtType, i);
else if ((flags & ResolveFlags.IDynamicInterfaceCastable) != 0)
*ppGenericContext = pTgtType;

return true;
}
Expand All @@ -253,5 +259,13 @@ private static bool FindImplSlotInSimpleMap(MethodTable* pTgtType,
_ => pTgtType->InterfaceMap[usEncodedValue - StaticVirtualMethodContextSource.ContextFromFirstInterface]
};
}

public enum ResolveFlags
{
Variant = 0x1,
DefaultInterfaceImplementation = 0x2,
Static = 0x4,
IDynamicInterfaceCastable = 0x8,
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,10 @@
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:System.Runtime.CompilerServices.StaticClassConstructionContext</Target>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Internal.TypeSystem.LockFreeReaderHashtableOfPointers`2</Target>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:System.Reflection.MethodBase.GetParametersAsSpan</Target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ public static bool IsDynamicType(RuntimeTypeHandle typeHandle)
public static unsafe IntPtr ResolveStaticDispatchOnType(RuntimeTypeHandle instanceType, RuntimeTypeHandle interfaceType, int slot, out RuntimeTypeHandle genericContext)
{
MethodTable* genericContextPtr = default;
IntPtr result = RuntimeImports.RhResolveDispatchOnType(instanceType.ToMethodTable(), interfaceType.ToMethodTable(), checked((ushort)slot), &genericContextPtr);
IntPtr result = RuntimeImports.RhResolveStaticDispatchOnType(instanceType.ToMethodTable(), interfaceType.ToMethodTable(), checked((ushort)slot), &genericContextPtr);
if (result != IntPtr.Zero)
genericContext = new RuntimeTypeHandle(genericContextPtr);
else
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime;

using Debug = System.Diagnostics.Debug;

namespace Internal.Runtime.CompilerHelpers
Expand All @@ -12,15 +14,13 @@ internal static class SharedCodeHelpers
{
public static unsafe MethodTable* GetOrdinalInterface(MethodTable* pType, ushort interfaceIndex)
{
Debug.Assert(interfaceIndex <= pType->NumInterfaces);
Debug.Assert(interfaceIndex < pType->NumInterfaces);
return pType->InterfaceMap[interfaceIndex];
}

public static unsafe MethodTable* GetCurrentSharedThunkContext()
{
// TODO: We should return the current context from the ThunkPool
// https://github.com/dotnet/runtimelab/issues/1442
return null;
return (MethodTable*)RuntimeImports.GetCurrentInteropThunkContext();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
using System;
using System.Runtime;
using System.Runtime.InteropServices;
using System.Threading;

using Internal.Runtime.Augments;
using Internal.TypeSystem;

using Debug = System.Diagnostics.Debug;

namespace Internal.Runtime
{
Expand All @@ -15,6 +21,8 @@ internal static bool IDynamicCastableIsInterfaceImplemented(IDynamicInterfaceCas
return instance.IsInterfaceImplemented(new RuntimeTypeHandle(interfaceType), throwIfNotImplemented);
}

private static readonly object s_thunkPoolHeap = RuntimeAugments.CreateThunksHeap(RuntimeImports.GetInteropCommonStubAddress());

[RuntimeExport("IDynamicCastableGetInterfaceImplementation")]
internal static IntPtr IDynamicCastableGetInterfaceImplementation(IDynamicInterfaceCastable instance, MethodTable* interfaceType, ushort slot)
{
Expand All @@ -28,11 +36,30 @@ internal static IntPtr IDynamicCastableGetInterfaceImplementation(IDynamicInterf
{
ThrowInvalidOperationException(implType);
}
IntPtr result = RuntimeImports.RhResolveDispatchOnType(implType, interfaceType, slot);

MethodTable* genericContext = null;
IntPtr result = RuntimeImports.RhResolveDynamicInterfaceCastableDispatchOnType(implType, interfaceType, slot, &genericContext);
if (result == IntPtr.Zero)
{
IDynamicCastableGetInterfaceImplementationFailure(instance, interfaceType, implType);
}

if (genericContext != null)
{
if (!s_thunkHashtable.TryGetValue(new InstantiatingThunkKey(result, (nint)genericContext), out nint thunk))
{
thunk = RuntimeAugments.AllocateThunk(s_thunkPoolHeap);
RuntimeAugments.SetThunkData(s_thunkPoolHeap, thunk, (nint)genericContext, result);
nint thunkInHashtable = s_thunkHashtable.AddOrGetExisting(thunk);
if (thunkInHashtable != thunk)
{
RuntimeAugments.FreeThunk(s_thunkPoolHeap, thunk);
thunk = thunkInHashtable;
}
}

result = thunk;
}
return result;
}

Expand Down Expand Up @@ -67,5 +94,46 @@ private static void IDynamicCastableGetInterfaceImplementationFailure(object ins

throw new EntryPointNotFoundException();
}

private static readonly InstantiatingThunkHashtable s_thunkHashtable = new InstantiatingThunkHashtable();

private class InstantiatingThunkHashtable : LockFreeReaderHashtableOfPointers<InstantiatingThunkKey, nint>
{
protected override bool CompareKeyToValue(InstantiatingThunkKey key, nint value)
{
bool result = RuntimeAugments.TryGetThunkData(s_thunkPoolHeap, value, out nint context, out nint target);
Debug.Assert(result);
return key.Target == target && key.Context == context;
}

protected override bool CompareValueToValue(nint value1, nint value2)
{
bool result1 = RuntimeAugments.TryGetThunkData(s_thunkPoolHeap, value1, out nint context1, out nint target1);
Debug.Assert(result1);

bool result2 = RuntimeAugments.TryGetThunkData(s_thunkPoolHeap, value2, out nint context2, out nint target2);
Debug.Assert(result2);
return context1 == context2 && target1 == target2;
}

protected override nint ConvertIntPtrToValue(nint pointer) => pointer;
protected override nint ConvertValueToIntPtr(nint value) => value;
protected override nint CreateValueFromKey(InstantiatingThunkKey key) => throw new NotImplementedException();
protected override int GetKeyHashCode(InstantiatingThunkKey key) => HashCode.Combine(key.Target, key.Context);

protected override int GetValueHashCode(nint value)
{
bool result = RuntimeAugments.TryGetThunkData(s_thunkPoolHeap, value, out nint context, out nint target);
Debug.Assert(result);
return HashCode.Combine(target, context);
}
}

private struct InstantiatingThunkKey
{
public readonly nint Target;
public readonly nint Context;
public InstantiatingThunkKey(nint target, nint context) => (Target, Context) = (target, context);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,9 @@
<Compile Include="$(CompilerCommonPath)\TypeSystem\Common\Utilities\LockFreeReaderHashtable.cs">
<Link>Utilities\LockFreeReaderHashtable.cs</Link>
</Compile>
<Compile Include="$(CompilerCommonPath)\TypeSystem\Common\Utilities\LockFreeReaderHashtableOfPointers.cs">
<Link>Utilities\LockFreeReaderHashtableOfPointers.cs</Link>
</Compile>
<Compile Include="$(AotCommonPath)\System\Collections\Generic\LowLevelList.cs">
<Link>System\Collections\Generic\LowLevelList.cs</Link>
</Compile>
Expand Down
Loading
Loading