Skip to content

Commit

Permalink
Implement function pointer type support
Browse files Browse the repository at this point in the history
Implements function pointer type support in the compiler, runtime, and reflection stack.

Contributes to dotnet#71883.

* Introduce a new kind of `MethodTable` for function pointers in the compiler.
* Remove function pointer type blocking from various places in the compiler.
* Generate a new summary table so we can construct/deconstruct function pointer types at runtime.
* Update metadata format so that we can capture calling conventions and distinguish managed/unmanaged function pointers in metadata.
* Update `MethodTable` reader.
* Update casting logic in the runtime.
* Update BCL (arrays, TypedReference, etc.)
* Update reflection stack: representation of the type, parsing metadata, invoke, field get/set
* Enable applicable tests.

Not implemented yet: the modified type stuff, runtime type loader support.
  • Loading branch information
MichalStrehovsky committed Apr 19, 2023
1 parent eb71e48 commit 3d97fb9
Show file tree
Hide file tree
Showing 55 changed files with 851 additions and 198 deletions.
14 changes: 13 additions & 1 deletion src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ internal bool IsString
get
{
// String is currently the only non-array type with a non-zero component size.
return ComponentSize == StringComponentSize.Value && !IsArray && !IsGenericTypeDefinition;
return ComponentSize == StringComponentSize.Value && IsCanonical;
}
}

Expand Down Expand Up @@ -720,6 +720,14 @@ internal bool IsParameterizedType
}
}

internal bool IsFunctionPointerType
{
get
{
return Kind == EETypeKind.FunctionPointerEEType;
}
}

// The parameterized type shape defines the particular form of parameterized type that
// is being represented.
// Currently, the meaning is a shape of 0 indicates that this is a Pointer,
Expand Down Expand Up @@ -945,6 +953,9 @@ internal MethodTable* BaseType
return null;
}

// Function pointers naturally set the base type field to null.
Debug.Assert(!IsFunctionPointerType || (!IsRelatedTypeViaIAT && _relatedType._pBaseType == null));

Debug.Assert(IsCanonical);

if (IsRelatedTypeViaIAT)
Expand All @@ -957,6 +968,7 @@ internal MethodTable* BaseType
{
Debug.Assert(IsDynamicType);
Debug.Assert(!IsParameterizedType);
Debug.Assert(!IsFunctionPointerType);
Debug.Assert(IsCanonical);
_uFlags &= (uint)~EETypeFlags.RelatedTypeViaIATFlag;
_relatedType._pBaseType = value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,43 +45,6 @@ internal IntPtr GetClasslibFunction(ClassLibFunctionId id)
return (IntPtr)InternalCalls.RhpGetClasslibFunctionFromEEType((MethodTable*)Unsafe.AsPointer(ref this), id);
}

// Returns an address in the module most closely associated with this MethodTable that can be handed to
// EH.GetClasslibException and use to locate the compute the correct exception type. In most cases
// this is just the MethodTable pointer itself, but when this type represents a generic that has been
// unified at runtime (and thus the MethodTable pointer resides in the process heap rather than a specific
// module) we need to do some work.
internal unsafe MethodTable* GetAssociatedModuleAddress()
{
fixed (MethodTable* pThis = &this)
{
if (!IsDynamicType)
return pThis;

// There are currently four types of runtime allocated EETypes, arrays, pointers, byrefs, and generic types.
// Arrays/Pointers/ByRefs can be handled by looking at their element type.
if (IsParameterizedType)
return pThis->RelatedParameterType->GetAssociatedModuleAddress();

if (!IsGeneric)
{
// No way to resolve module information for a non-generic dynamic type.
return null;
}

// Generic types are trickier. Often we could look at the parent type (since eventually it
// would derive from the class library's System.Object which is definitely not runtime
// allocated). But this breaks down for generic interfaces. Instead we fetch the generic
// instantiation information and use the generic type definition, which will always be module
// local. We know this lookup will succeed since we're dealing with a unified generic type
// and the unification process requires this metadata.
MethodTable* pGenericType = pThis->GenericDefinition;

Debug.Assert(pGenericType != null, "Generic type expected");

return pGenericType;
}
}

/// <summary>
/// Return true if type is good for simple casting : canonical, no related type via IAT, no generic variance
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public static unsafe object RhNewObject(MethodTable* pEEType)
!pEEType->IsInterface &&
!pEEType->IsArray &&
!pEEType->IsString &&
!pEEType->IsPointerType &&
!pEEType->IsFunctionPointerType &&
!pEEType->IsByRefLike;
if (!isValid)
Debug.Assert(false);
Expand Down Expand Up @@ -394,7 +396,7 @@ internal static unsafe IntPtr RhGetRuntimeHelperForType(MethodTable* pEEType, Ru
return (IntPtr)(delegate*<MethodTable*, object, object>)&TypeCast.IsInstanceOfArray;
else if (pEEType->IsInterface)
return (IntPtr)(delegate*<MethodTable*, object, object>)&TypeCast.IsInstanceOfInterface;
else if (pEEType->IsParameterizedType)
else if (pEEType->IsParameterizedType || pEEType->IsFunctionPointerType)
return (IntPtr)(delegate*<MethodTable*, object, object>)&TypeCast.IsInstanceOf; // Array handled above; pointers and byrefs handled here
else
return (IntPtr)(delegate*<MethodTable*, object, object>)&TypeCast.IsInstanceOfClass;
Expand All @@ -404,7 +406,7 @@ internal static unsafe IntPtr RhGetRuntimeHelperForType(MethodTable* pEEType, Ru
return (IntPtr)(delegate*<MethodTable*, object, object>)&TypeCast.CheckCastArray;
else if (pEEType->IsInterface)
return (IntPtr)(delegate*<MethodTable*, object, object>)&TypeCast.CheckCastInterface;
else if (pEEType->IsParameterizedType)
else if (pEEType->IsParameterizedType || pEEType->IsFunctionPointerType)
return (IntPtr)(delegate*<MethodTable*, object, object>)&TypeCast.CheckCast; // Array handled above; pointers and byrefs handled here
else
return (IntPtr)(delegate*<MethodTable*, object, object>)&TypeCast.CheckCastClass;
Expand Down
35 changes: 29 additions & 6 deletions src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/TypeCast.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public static unsafe object IsInstanceOfClass(MethodTable* pTargetType, object o
MethodTable* pObjType = obj.GetMethodTable();

Debug.Assert(!pTargetType->IsParameterizedType, "IsInstanceOfClass called with parameterized MethodTable");
Debug.Assert(!pTargetType->IsFunctionPointerType, "IsInstanceOfClass called with function pointer MethodTable");
Debug.Assert(!pTargetType->IsInterface, "IsInstanceOfClass called with interface MethodTable");

// Quick check if both types are good for simple casting: canonical, no related type via IAT, no generic variance
Expand Down Expand Up @@ -251,6 +252,7 @@ private static unsafe bool IsInstanceOfInterfaceViaIDynamicInterfaceCastable(Met
internal static unsafe bool ImplementsInterface(MethodTable* pObjType, MethodTable* pTargetType, EETypePairList* pVisited)
{
Debug.Assert(!pTargetType->IsParameterizedType, "did not expect parameterized type");
Debug.Assert(!pTargetType->IsFunctionPointerType, "did not expect function pointer type");
Debug.Assert(pTargetType->IsInterface, "IsInstanceOfInterface called with non-interface MethodTable");

int numInterfaces = pObjType->NumInterfaces;
Expand Down Expand Up @@ -536,22 +538,30 @@ internal static unsafe bool AreTypesAssignableInternalUncached(MethodTable* pSou
if (pSourceType->IsParameterizedType
&& (pTargetType->ParameterizedTypeShape == pSourceType->ParameterizedTypeShape))
{
MethodTable* pSourceRelatedParameterType = pSourceType->RelatedParameterType;
// Source type is also a parameterized type. Are the parameter types compatible?
if (pSourceType->RelatedParameterType->IsPointerType)
if (pSourceRelatedParameterType->IsPointerType)
{
// If the parameter types are pointers, then only exact matches are correct.
// As we've already called AreTypesEquivalent at the start of this function,
// return false as the exact match case has already been handled.
// int** is not compatible with uint**, nor is int*[] oompatible with uint*[].
return false;
}
else if (pSourceType->RelatedParameterType->IsByRefType)
else if (pSourceRelatedParameterType->IsByRefType)
{
// Only allow exact matches for ByRef types - same as pointers above. This should
// be unreachable and it's only a defense in depth. ByRefs can't be parameters
// of any parameterized type.
return false;
}
else if (pSourceRelatedParameterType->IsFunctionPointerType)
{
// If the parameter types are function pointers, then only exact matches are correct.
// As we've already called AreTypesEquivalent at the start of this function,
// return false as the exact match case has already been handled.
return false;
}
else
{
// Note that using AreTypesAssignableInternal with AssignmentVariation.AllowSizeEquivalence
Expand All @@ -565,6 +575,13 @@ internal static unsafe bool AreTypesAssignableInternalUncached(MethodTable* pSou
// Can't cast a non-parameter type to a parameter type or a parameter type of different shape to a parameter type
return false;
}

if (pTargetType->IsFunctionPointerType)
{
// Function pointers only cast if source and target are equivalent types.
return false;
}

if (pSourceType->IsArray)
{
// Target type is not an array. But we can still cast arrays to Object or System.Array.
Expand All @@ -574,6 +591,10 @@ internal static unsafe bool AreTypesAssignableInternalUncached(MethodTable* pSou
{
return false;
}
else if (pSourceType->IsFunctionPointerType)
{
return false;
}

//
// Handle cast to other (non-interface, non-array) cases.
Expand Down Expand Up @@ -829,9 +850,11 @@ internal static unsafe bool IsDerived(MethodTable* pDerivedType, MethodTable* pB
{
Debug.Assert(!pDerivedType->IsArray, "did not expect array type");
Debug.Assert(!pDerivedType->IsParameterizedType, "did not expect parameterType");
Debug.Assert(!pDerivedType->IsFunctionPointerType, "did not expect function pointer");
Debug.Assert(!pBaseType->IsArray, "did not expect array type");
Debug.Assert(!pBaseType->IsInterface, "did not expect interface type");
Debug.Assert(!pBaseType->IsParameterizedType, "did not expect parameterType");
Debug.Assert(!pBaseType->IsFunctionPointerType, "did not expect function pointer");
Debug.Assert(pBaseType->IsCanonical || pBaseType->IsGenericTypeDefinition, "unexpected MethodTable");
Debug.Assert(pDerivedType->IsCanonical || pDerivedType->IsGenericTypeDefinition, "unexpected MethodTable");

Expand Down Expand Up @@ -879,7 +902,7 @@ public static unsafe object IsInstanceOf(MethodTable* pTargetType, object obj)
return IsInstanceOfArray(pTargetType, obj);
else if (pTargetType->IsInterface)
return IsInstanceOfInterface(pTargetType, obj);
else if (pTargetType->IsParameterizedType)
else if (pTargetType->IsParameterizedType || pTargetType->IsFunctionPointerType)
return null; // We handled arrays above so this is for pointers and byrefs only.
else
return IsInstanceOfClass(pTargetType, obj);
Expand Down Expand Up @@ -921,13 +944,13 @@ public static unsafe object CheckCast(MethodTable* pTargetType, object obj)
return CheckCastArray(pTargetType, obj);
else if (pTargetType->IsInterface)
return CheckCastInterface(pTargetType, obj);
else if (pTargetType->IsParameterizedType)
return CheckCastNonArrayParameterizedType(pTargetType, obj);
else if (pTargetType->IsParameterizedType || pTargetType->IsFunctionPointerType)
return CheckCastUnboxableType(pTargetType, obj);
else
return CheckCastClass(pTargetType, obj);
}

private static unsafe object CheckCastNonArrayParameterizedType(MethodTable* pTargetType, object obj)
private static unsafe object CheckCastUnboxableType(MethodTable* pTargetType, object obj)
{
// a null value can be cast to anything
if (obj == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,10 @@
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Internal.Metadata.NativeFormat.ScopeReferenceHandle</Target>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Internal.Metadata.NativeFormat.SignatureCallingConvention</Target>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Internal.Metadata.NativeFormat.SingleCollection</Target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,23 @@ public Type GetPointerTypeForHandle(RuntimeTypeHandle typeHandle)
return targetTypeHandle.GetTypeForRuntimeTypeHandle().GetPointerType(typeHandle);
}

public Type GetFunctionPointerTypeForHandle(RuntimeTypeHandle typeHandle)
{
ExecutionEnvironment.GetFunctionPointerTypeComponents(typeHandle, out RuntimeTypeHandle returnTypeHandle,
out RuntimeTypeHandle[] parameterHandles,
out bool isUnmanaged);

RuntimeTypeInfo returnType = returnTypeHandle.GetTypeForRuntimeTypeHandle();
int count = parameterHandles.Length;
RuntimeTypeInfo[] parameterTypes = new RuntimeTypeInfo[count];
for (int i = 0; i < count; i++)
{
parameterTypes[i] = parameterHandles[i].GetTypeForRuntimeTypeHandle();
}

return RuntimeFunctionPointerTypeInfo.GetFunctionPointerTypeInfo(returnType, parameterTypes, isUnmanaged, typeHandle);
}

public Type GetByRefTypeForHandle(RuntimeTypeHandle typeHandle)
{
RuntimeTypeHandle targetTypeHandle;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ public abstract class ExecutionEnvironment

public abstract bool TryGetMultiDimArrayTypeForElementType(RuntimeTypeHandle elementTypeHandle, int rank, out RuntimeTypeHandle arrayTypeHandle);

public abstract bool TryGetFunctionPointerTypeForComponents(RuntimeTypeHandle returnTypeHandle, RuntimeTypeHandle[] parameterHandles, bool isUnmanaged, out RuntimeTypeHandle functionPointerTypeHandle);
public abstract void GetFunctionPointerTypeComponents(RuntimeTypeHandle functionPointerHandle, out RuntimeTypeHandle returnTypeHandle, out RuntimeTypeHandle[] parameterHandles, out bool isUnmanaged);

public abstract bool TryGetPointerTypeForTargetType(RuntimeTypeHandle targetTypeHandle, out RuntimeTypeHandle pointerTypeHandle);
public abstract bool TryGetPointerTypeTargetType(RuntimeTypeHandle pointerTypeHandle, out RuntimeTypeHandle targetTypeHandle);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ public static Type GetRuntimeTypeBypassCache(EETypePtr eeType)
{
return callbacks.GetPointerTypeForHandle(runtimeTypeHandle);
}
else if (eeType.IsFunctionPointer)
{
return callbacks.GetFunctionPointerTypeForHandle(runtimeTypeHandle);
}
else if (eeType.IsByRef)
{
return callbacks.GetByRefTypeForHandle(runtimeTypeHandle);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public abstract class ReflectionExecutionDomainCallbacks
public abstract Type GetArrayTypeForHandle(RuntimeTypeHandle typeHandle);
public abstract Type GetMdArrayTypeForHandle(RuntimeTypeHandle typeHandle, int rank);
public abstract Type GetPointerTypeForHandle(RuntimeTypeHandle typeHandle);
public abstract Type GetFunctionPointerTypeForHandle(RuntimeTypeHandle typeHandle);
public abstract Type GetByRefTypeForHandle(RuntimeTypeHandle typeHandle);
public abstract Type GetConstructedGenericTypeForHandle(RuntimeTypeHandle typeHandle);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ public static bool IsInstanceOfInterface(object obj, RuntimeTypeHandle interface
public static bool TryGetBaseType(RuntimeTypeHandle typeHandle, out RuntimeTypeHandle baseTypeHandle)
{
EETypePtr eeType = typeHandle.ToEETypePtr();
if (eeType.IsGenericTypeDefinition || eeType.IsPointer || eeType.IsByRef)
if (eeType.IsGenericTypeDefinition || eeType.IsPointer || eeType.IsByRef || eeType.IsFunctionPointer)
{
baseTypeHandle = default(RuntimeTypeHandle);
return false;
Expand All @@ -490,7 +490,7 @@ public static bool TryGetBaseType(RuntimeTypeHandle typeHandle, out RuntimeTypeH
public static IEnumerable<RuntimeTypeHandle> TryGetImplementedInterfaces(RuntimeTypeHandle typeHandle)
{
EETypePtr eeType = typeHandle.ToEETypePtr();
if (eeType.IsGenericTypeDefinition || eeType.IsPointer || eeType.IsByRef)
if (eeType.IsGenericTypeDefinition || eeType.IsPointer || eeType.IsByRef || eeType.IsFunctionPointer)
return null;

LowLevelList<RuntimeTypeHandle> implementedInterfaces = new LowLevelList<RuntimeTypeHandle>();
Expand Down Expand Up @@ -637,6 +637,11 @@ public static bool IsUnmanagedPointerType(RuntimeTypeHandle typeHandle)
return typeHandle.ToEETypePtr().IsPointer;
}

public static bool IsFunctionPointerType(RuntimeTypeHandle typeHandle)
{
return typeHandle.ToEETypePtr().IsFunctionPointer;
}

public static bool IsByRefType(RuntimeTypeHandle typeHandle)
{
return typeHandle.ToEETypePtr().IsByRef;
Expand All @@ -659,6 +664,8 @@ public static bool CanPrimitiveWiden(RuntimeTypeHandle srcType, RuntimeTypeHandl
return false;
if (srcEEType.IsPointer || dstEEType.IsPointer)
return false;
if (srcEEType.IsFunctionPointer || dstEEType.IsFunctionPointer)
return false;
if (srcEEType.IsByRef || dstEEType.IsByRef)
return false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@
<Compile Include="System\Reflection\Metadata\AssemblyExtensions.cs" />
<Compile Include="System\Reflection\Metadata\MetadataUpdater.cs" />
<Compile Include="System\Reflection\ModifiedType.NativeAot.cs" />
<Compile Include="System\Reflection\Runtime\TypeInfos\RuntimeFunctionPointerTypeInfo.cs" />
<Compile Include="System\Reflection\Runtime\TypeInfos\RuntimeFunctionPointerTypeInfo.UnificationKey.cs" />
<Compile Include="System\Runtime\InteropServices\CriticalHandle.NativeAot.cs" />
<Compile Include="System\Activator.NativeAot.cs" />
<Compile Include="System\AppContext.NativeAot.cs" />
Expand Down
Loading

0 comments on commit 3d97fb9

Please sign in to comment.