Skip to content

Commit

Permalink
Implement function pointer type support (#85031)
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 #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 20, 2023
1 parent db7ca5d commit 66ba9d5
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 IntPtr FinalizerCode
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 IntPtr FinalizerCode
{
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 CheckCastNonboxableType(pTargetType, obj);
else
return CheckCastClass(pTargetType, obj);
}

private static unsafe object CheckCastNonArrayParameterizedType(MethodTable* pTargetType, object obj)
private static unsafe object CheckCastNonboxableType(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 66ba9d5

Please sign in to comment.