From 19d545c62c3eea1836e8c3830eb654e2a022cac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 25 Apr 2023 17:51:41 +0900 Subject: [PATCH] Add function pointer type support to type loader (#85287) So that we can create new function pointer types at runtime within the context of `MakeGenericXXX`. --- .../src/Internal/Runtime/MethodTable.cs | 4 +- .../nativeaot/Runtime/inc/MethodTable.h | 1 + .../Runtime/TypeLoader/EETypeCreator.cs | 28 +++++- .../TypeLoader/NativeLayoutInfoLoadContext.cs | 24 ++++- .../Runtime/TypeLoader/TypeBuilder.cs | 48 +++++++--- .../Runtime/TypeLoader/TypeBuilderState.cs | 13 ++- .../Internal/TypeSystem/TypeDesc.Runtime.cs | 31 +++++++ .../TypeSystem/TypeSystemContext.Runtime.cs | 62 +++++++++++++ .../Internal/NativeFormat/NativeFormat.cs | 1 + .../NativeFormat/NativeFormatWriter.cs | 37 ++++++++ .../NativeLayoutVertexNode.cs | 25 ++++- .../NodeFactory.NativeLayout.cs | 20 ++-- .../SmokeTests/UnitTests/Generics.cs | 92 +++++++++++++++++++ 13 files changed, 352 insertions(+), 34 deletions(-) diff --git a/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs b/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs index 61fc334daead9..0f8a9feb4ba55 100644 --- a/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs +++ b/src/coreclr/nativeaot/Common/src/Internal/Runtime/MethodTable.cs @@ -1568,6 +1568,7 @@ internal static uint GetSizeofEEType( bool fRequiresOptionalFields, bool fHasSealedVirtuals, bool fHasGenericInfo, + int cFunctionPointerTypeParameters, bool fHasNonGcStatics, bool fHasGcStatics, bool fHasThreadStatics) @@ -1580,6 +1581,7 @@ internal static uint GetSizeofEEType( (fHasFinalizer ? sizeof(UIntPtr) : 0) + (fRequiresOptionalFields ? sizeof(IntPtr) : 0) + (fHasSealedVirtuals ? sizeof(IntPtr) : 0) + + cFunctionPointerTypeParameters * sizeof(IntPtr) + (fHasGenericInfo ? sizeof(IntPtr)*2 : 0) + // pointers to GenericDefinition and GenericComposition (fHasNonGcStatics ? sizeof(IntPtr) : 0) + // pointer to data (fHasGcStatics ? sizeof(IntPtr) : 0) + // pointer to data @@ -1740,7 +1742,7 @@ public MethodTable* this[int index] if (((nint)_pFirst & IsRelative) != 0) return (((RelativePointer*)((nint)_pFirst - IsRelative)) + index)->Value; - return (MethodTable*)_pFirst + index; + return *(MethodTable**)_pFirst + index; } #if TYPE_LOADER_IMPLEMENTATION set diff --git a/src/coreclr/nativeaot/Runtime/inc/MethodTable.h b/src/coreclr/nativeaot/Runtime/inc/MethodTable.h index e194bbdc70018..555c2c8aaeabf 100644 --- a/src/coreclr/nativeaot/Runtime/inc/MethodTable.h +++ b/src/coreclr/nativeaot/Runtime/inc/MethodTable.h @@ -75,6 +75,7 @@ enum EETypeElementType : uint8_t ElementType_SzArray = 0x18, ElementType_ByRef = 0x19, ElementType_Pointer = 0x1A, + ElementType_FunctionPointer = 0x1B, }; //------------------------------------------------------------------------------------------------- diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/EETypeCreator.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/EETypeCreator.cs index 0f7ec21048475..f77e10b0ff91d 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/EETypeCreator.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/EETypeCreator.cs @@ -189,6 +189,7 @@ private static void CreateEETypeWorker(MethodTable* pTemplateEEType, uint hashCo flags |= (uint)EETypeFlags.IsDynamicTypeFlag; + int numFunctionPointerTypeParameters = 0; if (state.TypeBeingBuilt.IsMdArray) { // If we're building an MDArray, the template is object[,] and we @@ -197,6 +198,17 @@ private static void CreateEETypeWorker(MethodTable* pTemplateEEType, uint hashCo 2 * IntPtr.Size + // EETypePtr + Length state.ArrayRank.Value * sizeof(int) * 2; // 2 ints per rank for bounds } + else if (state.TypeBeingBuilt.IsFunctionPointer) + { + // Base size encodes number of parameters and calling convention + MethodSignature sig = ((FunctionPointerType)state.TypeBeingBuilt).Signature; + baseSize = (sig.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask) switch + { + 0 => sig.Length, + _ => sig.Length | unchecked((int)FunctionPointerFlags.IsUnmanaged), + }; + numFunctionPointerTypeParameters = sig.Length; + } // Optional fields encoding int cbOptionalFieldsSize; @@ -250,6 +262,7 @@ private static void CreateEETypeWorker(MethodTable* pTemplateEEType, uint hashCo cbOptionalFieldsSize > 0, (rareFlags & (int)EETypeRareFlags.HasSealedVTableEntriesFlag) != 0, isGeneric, + numFunctionPointerTypeParameters, allocatedNonGCDataSize != 0, state.GcDataSize != 0, state.ThreadDataSize != 0); @@ -666,7 +679,7 @@ public static RuntimeTypeHandle CreateEEType(TypeDesc type, TypeBuilderState sta MethodTable* pTemplateEEType; - if (type is PointerType || type is ByRefType) + if (type is PointerType || type is ByRefType || type is FunctionPointerType) { Debug.Assert(0 == state.NonGcDataSize); Debug.Assert(false == state.HasStaticConstructor); @@ -675,8 +688,17 @@ public static RuntimeTypeHandle CreateEEType(TypeDesc type, TypeBuilderState sta Debug.Assert(IntPtr.Zero == state.GcStaticDesc); Debug.Assert(IntPtr.Zero == state.ThreadStaticDesc); - // Pointers and ByRefs only differ by the ParameterizedTypeShape and ElementType value. - RuntimeTypeHandle templateTypeHandle = typeof(void*).TypeHandle; + RuntimeTypeHandle templateTypeHandle; + if (type is FunctionPointerType) + { + // There's still differences to paper over, but `delegate*` is close enough. + templateTypeHandle = typeof(delegate*).TypeHandle; + } + else + { + // Pointers and ByRefs only differ by the ParameterizedTypeShape and ElementType value. + templateTypeHandle = typeof(void*).TypeHandle; + } pTemplateEEType = templateTypeHandle.ToEETypePtr(); } diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/NativeLayoutInfoLoadContext.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/NativeLayoutInfoLoadContext.cs index bf8795ad50f1e..f3c356f5ce628 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/NativeLayoutInfoLoadContext.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/NativeLayoutInfoLoadContext.cs @@ -125,7 +125,7 @@ internal TypeDesc GetType(ref NativeParser parser) case TypeSignatureKind.MultiDimArray: { - DefType elementType = (DefType)GetType(ref parser); + TypeDesc elementType = GetType(ref parser); int rank = (int)data; // Skip encoded bounds and lobounds @@ -150,9 +150,25 @@ internal TypeDesc GetType(ref NativeParser parser) return _typeSystemContext.GetWellKnownType((WellKnownType)data); case TypeSignatureKind.FunctionPointer: - Debug.Fail("NYI!"); - NativeParser.ThrowBadImageFormatException(); - return null; + { + var callConv = (MethodCallingConvention)parser.GetUnsigned(); + Debug.Assert((callConv & MethodCallingConvention.Generic) == 0); + + uint numParams = parser.GetUnsigned(); + + TypeDesc returnType = GetType(ref parser); + TypeDesc[] parameters = new TypeDesc[numParams]; + for (uint i = 0; i < parameters.Length; i++) + parameters[i] = GetType(ref parser); + + return _typeSystemContext.GetFunctionPointerType( + new MethodSignature( + (callConv & MethodCallingConvention.Unmanaged) != 0 ? MethodSignatureFlags.UnmanagedCallingConvention : 0, + 0, + returnType, + parameters + )); + } default: NativeParser.ThrowBadImageFormatException(); diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs index 299d25df24a32..33431b3ec32dd 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilder.cs @@ -159,7 +159,7 @@ public void PrepareMethod(MethodDesc method) private void InsertIntoNeedsTypeHandleList(TypeDesc type) { - if ((type is DefType) || (type is ArrayType) || (type is PointerType) || (type is ByRefType)) + if ((type is DefType) || (type is ArrayType) || (type is PointerType) || (type is ByRefType) || (type is FunctionPointerType)) { _typesThatNeedTypeHandles.Add(type); } @@ -233,7 +233,7 @@ internal void PrepareType(TypeDesc type) if (type is ArrayType typeAsArrayType) { - if (typeAsArrayType.IsSzArray && !typeAsArrayType.ElementType.IsPointer) + if (typeAsArrayType.IsSzArray && !typeAsArrayType.ElementType.IsPointer && !typeAsArrayType.ElementType.IsFunctionPointer) { TypeDesc.ComputeTemplate(state); Debug.Assert(state.TemplateType != null && state.TemplateType is ArrayType && !state.TemplateType.RuntimeTypeHandle.IsNull()); @@ -242,10 +242,16 @@ internal void PrepareType(TypeDesc type) } else { - Debug.Assert(typeAsArrayType.IsMdArray || typeAsArrayType.ElementType.IsPointer); + Debug.Assert(typeAsArrayType.IsMdArray || typeAsArrayType.ElementType.IsPointer || typeAsArrayType.ElementType.IsFunctionPointer); } } } + else if (type is FunctionPointerType functionPointerType) + { + RegisterForPreparation(functionPointerType.Signature.ReturnType); + foreach (TypeDesc paramType in functionPointerType.Signature) + RegisterForPreparation(paramType); + } else { Debug.Assert(false); @@ -590,7 +596,7 @@ private unsafe void AllocateRuntimeType(TypeDesc type) { TypeBuilderState state = type.GetTypeBuilderState(); - Debug.Assert(type is DefType || type is ArrayType || type is PointerType || type is ByRefType); + Debug.Assert(type is DefType || type is ArrayType || type is PointerType || type is ByRefType || type is FunctionPointerType); RuntimeTypeHandle rtt = EETypeCreator.CreateEEType(type, state); @@ -843,6 +849,19 @@ private void FinishRuntimeType(TypeDesc type) } } } + else if (type is FunctionPointerType) + { + MethodSignature sig = ((FunctionPointerType)type).Signature; + unsafe + { + MethodTable* halfBakedMethodTable = state.HalfBakedRuntimeTypeHandle.ToEETypePtr(); + halfBakedMethodTable->FunctionPointerReturnType = GetRuntimeTypeHandle(sig.ReturnType).ToEETypePtr(); + Debug.Assert(halfBakedMethodTable->NumFunctionPointerParameters == sig.Length); + MethodTableList paramList = halfBakedMethodTable->FunctionPointerParameters; + for (int i = 0; i < sig.Length; i++) + paramList[i] = GetRuntimeTypeHandle(sig[i]).ToEETypePtr(); + } + } else { Debug.Assert(false); @@ -942,24 +961,25 @@ private void FinishTypeAndMethodBuilding() int newArrayTypesCount = 0; int newPointerTypesCount = 0; int newByRefTypesCount = 0; + int newFunctionPointerTypesCount = 0; int[] mdArrayNewTypesCount = null; for (int i = 0; i < _typesThatNeedTypeHandles.Count; i++) { - ParameterizedType typeAsParameterizedType = _typesThatNeedTypeHandles[i] as ParameterizedType; - if (typeAsParameterizedType == null) - continue; + TypeDesc type = _typesThatNeedTypeHandles[i]; - if (typeAsParameterizedType.IsSzArray) + if (type.IsSzArray) newArrayTypesCount++; - else if (typeAsParameterizedType.IsPointer) + else if (type.IsPointer) newPointerTypesCount++; - else if (typeAsParameterizedType.IsByRef) + else if (type.IsFunctionPointer) + newFunctionPointerTypesCount++; + else if (type.IsByRef) newByRefTypesCount++; - else if (typeAsParameterizedType.IsMdArray) + else if (type.IsMdArray) { mdArrayNewTypesCount ??= new int[MDArray.MaxRank + 1]; - mdArrayNewTypesCount[((ArrayType)typeAsParameterizedType).Rank]++; + mdArrayNewTypesCount[((ArrayType)type).Rank]++; } } // Reserve space in array/pointer cache's so that the actual adding can be fault-free. @@ -981,6 +1001,7 @@ private void FinishTypeAndMethodBuilding() TypeSystemContext.PointerTypesCache.Reserve(TypeSystemContext.PointerTypesCache.Count + newPointerTypesCount); TypeSystemContext.ByRefTypesCache.Reserve(TypeSystemContext.ByRefTypesCache.Count + newByRefTypesCount); + TypeSystemContext.FunctionPointerTypesCache.Reserve(TypeSystemContext.FunctionPointerTypesCache.Count + newFunctionPointerTypesCount); // Finally, register all generic types and methods atomically with the runtime RegisterGenericTypesAndMethods(); @@ -1001,7 +1022,8 @@ private void FinishTypeAndMethodBuilding() { if (_typesThatNeedTypeHandles[i] is FunctionPointerType typeAsFunctionPointerType) { - throw new NotImplementedException(); + Debug.Assert(!typeAsFunctionPointerType.RuntimeTypeHandle.IsNull()); + TypeSystemContext.FunctionPointerTypesCache.AddOrGetExisting(typeAsFunctionPointerType.RuntimeTypeHandle); } continue; } diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilderState.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilderState.cs index b4348c1ed51b0..a95514e7bf0b3 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilderState.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeBuilderState.cs @@ -69,7 +69,8 @@ public TypeDesc TemplateType // Arrays of pointers don't implement generic interfaces and are special cases. They use // typeof(char*[]) as their template. - if (TypeBeingBuilt.IsSzArray && ((ArrayType)TypeBeingBuilt).ElementType.IsPointer) + if (TypeBeingBuilt.IsSzArray && ((ArrayType)TypeBeingBuilt).ElementType is TypeDesc elementType && + (elementType.IsPointer || elementType.IsFunctionPointer)) { _templateType = TypeBeingBuilt.Context.ResolveRuntimeTypeHandle(typeof(char*[]).TypeHandle); _templateTypeLoaderNativeLayout = false; @@ -252,12 +253,13 @@ private ushort ComputeNumVTableSlots() // Template type loader case unsafe { - if (TypeBeingBuilt.IsPointer || TypeBeingBuilt.IsByRef) + if (TypeBeingBuilt.IsPointer || TypeBeingBuilt.IsByRef || TypeBeingBuilt.IsFunctionPointer) { // Pointers and byrefs don't have vtable slots return 0; } - if (TypeBeingBuilt.IsMdArray || (TypeBeingBuilt.IsSzArray && ((ArrayType)TypeBeingBuilt).ElementType.IsPointer)) + if (TypeBeingBuilt.IsMdArray || (TypeBeingBuilt.IsSzArray && ((ArrayType)TypeBeingBuilt).ElementType is TypeDesc elementType + && (elementType.IsPointer || elementType.IsFunctionPointer))) { // MDArray types and pointer arrays have the same vtable as the System.Array type they "derive" from. // They do not implement the generic interfaces that make this interesting for normal arrays. @@ -355,7 +357,8 @@ public LowLevelList InstanceGCLayout Debug.Assert(TypeBeingBuilt.RetrieveRuntimeTypeHandleIfPossible() || TypeBeingBuilt.IsTemplateCanonical() || (TypeBeingBuilt is PointerType) || - (TypeBeingBuilt is ByRefType)); + (TypeBeingBuilt is ByRefType) || + (TypeBeingBuilt is FunctionPointerType)); _instanceGCLayout = s_emptyLayout; } } @@ -458,7 +461,7 @@ public bool IsArrayOfReferenceTypes { ArrayType typeAsArrayType = TypeBeingBuilt as ArrayType; if (typeAsArrayType != null) - return !typeAsArrayType.ParameterType.IsValueType && !typeAsArrayType.ParameterType.IsPointer; + return !typeAsArrayType.ParameterType.IsValueType && !typeAsArrayType.ParameterType.IsPointer && !typeAsArrayType.ParameterType.IsFunctionPointer; else return false; } diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/TypeDesc.Runtime.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/TypeDesc.Runtime.cs index 1a24545704913..63108ba3fcc3a 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/TypeDesc.Runtime.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/TypeDesc.Runtime.cs @@ -128,6 +128,37 @@ internal bool RetrieveRuntimeTypeHandleIfPossible() { // SignatureVariables do not have RuntimeTypeHandles } + else if (type is FunctionPointerType functionPointerType) + { + MethodSignature sig = functionPointerType.Signature; + if (sig.ReturnType.RetrieveRuntimeTypeHandleIfPossible()) + { + RuntimeTypeHandle[] parameterHandles = new RuntimeTypeHandle[sig.Length]; + bool handlesAvailable = true; + for (int i = 0; i < parameterHandles.Length; i++) + { + if (sig[i].RetrieveRuntimeTypeHandleIfPossible()) + { + parameterHandles[i] = sig[i].RuntimeTypeHandle; + } + else + { + handlesAvailable = false; + break; + } + } + + if (handlesAvailable + && TypeLoaderEnvironment.Instance.TryLookupFunctionPointerTypeForComponents( + sig.ReturnType.RuntimeTypeHandle, parameterHandles, + isUnmanaged: (sig.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask) != 0, + out RuntimeTypeHandle rtth)) + { + functionPointerType.SetRuntimeTypeHandleUnsafe(rtth); + return true; + } + } + } else { Debug.Assert(false); diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/TypeSystemContext.Runtime.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/TypeSystemContext.Runtime.cs index 450d0cb986366..e9796dce6164c 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/TypeSystemContext.Runtime.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/TypeSystem/TypeSystemContext.Runtime.cs @@ -71,6 +71,65 @@ protected override int GetValueHashCode(RuntimeTypeHandle value) } } + internal readonly struct FunctionPointerTypeKey + { + public readonly RuntimeTypeHandle ReturnType; + public readonly RuntimeTypeHandle[] ParameterTypes; + public readonly bool IsUnmanaged; + public FunctionPointerTypeKey(RuntimeTypeHandle returnType, RuntimeTypeHandle[] parameterTypes, bool isUnmanaged) + => (ReturnType, ParameterTypes, IsUnmanaged) = (returnType, parameterTypes, isUnmanaged); + } + + internal class FunctionPointerRuntimeTypeHandleHashtable : LockFreeReaderHashtableOfPointers + { + protected override bool CompareKeyToValue(FunctionPointerTypeKey key, RuntimeTypeHandle value) + { + if (key.IsUnmanaged != RuntimeAugments.IsUnmanagedFunctionPointerType(value) + || key.ParameterTypes.Length != RuntimeAugments.GetFunctionPointerParameterCount(value) + || !key.ReturnType.Equals(RuntimeAugments.GetFunctionPointerReturnType(value))) + return false; + + for (int i = 0; i < key.ParameterTypes.Length; i++) + if (!key.ParameterTypes[i].Equals(RuntimeAugments.GetFunctionPointerParameterType(value, i))) + return false; + + return true; + } + + protected override bool CompareValueToValue(RuntimeTypeHandle value1, RuntimeTypeHandle value2) + { + return value1.Equals(value2); + } + + protected override RuntimeTypeHandle ConvertIntPtrToValue(IntPtr pointer) + { + unsafe + { + return ((MethodTable*)pointer.ToPointer())->ToRuntimeTypeHandle(); + } + } + + protected override IntPtr ConvertValueToIntPtr(RuntimeTypeHandle value) + { + return value.ToIntPtr(); + } + + protected override RuntimeTypeHandle CreateValueFromKey(FunctionPointerTypeKey key) + { + throw new NotSupportedException(); + } + + protected override int GetKeyHashCode(FunctionPointerTypeKey key) + { + return TypeHashingAlgorithms.ComputeMethodSignatureHashCode(key.ReturnType.GetHashCode(), key.ParameterTypes); + } + + protected override int GetValueHashCode(RuntimeTypeHandle value) + { + return value.GetHashCode(); + } + } + internal static RuntimeTypeHandleToParameterTypeRuntimeTypeHandleHashtable[] s_ArrayTypesCaches = new RuntimeTypeHandleToParameterTypeRuntimeTypeHandleHashtable[MDArray.MaxRank + 1]; /// /// Cache of array types created by the builder to prevent duplication @@ -100,6 +159,9 @@ internal static RuntimeTypeHandleToParameterTypeRuntimeTypeHandleHashtable GetAr internal static RuntimeTypeHandleToParameterTypeRuntimeTypeHandleHashtable ByRefTypesCache { get; } = new RuntimeTypeHandleToParameterTypeRuntimeTypeHandleHashtable(); + internal static FunctionPointerRuntimeTypeHandleHashtable FunctionPointerTypesCache { get; } + = new FunctionPointerRuntimeTypeHandleHashtable(); + private TypeDesc[] ResolveRuntimeTypeHandlesInternal(RuntimeTypeHandle[] runtimeTypeHandles) { TypeDesc[] TypeDescs = new TypeDesc[runtimeTypeHandles.Length]; diff --git a/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormat.cs b/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormat.cs index a5645f39f0ade..7ccaa59a1a4c0 100644 --- a/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormat.cs +++ b/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormat.cs @@ -203,6 +203,7 @@ enum MethodCallingConvention : uint { Generic = 0x1, Static = 0x2, + Unmanaged = 0x4, }; #if NATIVEFORMAT_PUBLICWRITER diff --git a/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormatWriter.cs b/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormatWriter.cs index 2ab3363791b16..9c6f9a9401213 100644 --- a/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormatWriter.cs +++ b/src/coreclr/tools/Common/Internal/NativeFormat/NativeFormatWriter.cs @@ -518,6 +518,12 @@ public Vertex GetMDArrayTypeSignature(Vertex elementType, uint rank, uint[] boun MDArrayTypeSignature sig = new MDArrayTypeSignature(elementType, rank, bounds, lowerBounds); return Unify(sig); } + + public Vertex GetFunctionPointerTypeSignature(Vertex methodSignature) + { + FunctionPointerTypeSignature sig = new FunctionPointerTypeSignature(methodSignature); + return Unify(sig); + } } internal sealed class PlacedVertex : Vertex @@ -1462,6 +1468,37 @@ public override bool Equals(object obj) } } +#if NATIVEFORMAT_PUBLICWRITER + public +#else + internal +#endif + class FunctionPointerTypeSignature : Vertex + { + private Vertex _methodSignature; + + public FunctionPointerTypeSignature(Vertex methodSignature) + { + _methodSignature = methodSignature; + } + + internal override void Save(NativeWriter writer) + { + writer.WriteUnsigned((uint)TypeSignatureKind.FunctionPointer); + _methodSignature.Save(writer); + } + + public override int GetHashCode() + { + return _methodSignature.GetHashCode(); + } + + public override bool Equals(object obj) + { + return obj is FunctionPointerTypeSignature fnptrSig && _methodSignature.Equals(fnptrSig._methodSignature); + } + } + #if NATIVEFORMAT_PUBLICWRITER public #else diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs index a9aad5606a5a6..47de385a51920 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs @@ -344,6 +344,8 @@ public override Vertex WriteVertex(NodeFactory factory) methodCallingConvention |= MethodCallingConvention.Generic; if (_signature.IsStatic) methodCallingConvention |= MethodCallingConvention.Static; + if ((_signature.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask) != 0) + methodCallingConvention |= MethodCallingConvention.Unmanaged; Debug.Assert(_signature.Length == _parametersSig.Length); @@ -407,9 +409,8 @@ public static NativeLayoutTypeSignatureVertexNode NewTypeSignatureVertexNode(Nod case Internal.TypeSystem.TypeFlags.SignatureMethodVariable: return new NativeLayoutGenericVarSignatureVertexNode(type); - // TODO Internal.TypeSystem.TypeFlags.FunctionPointer (Runtime parsing also not yet implemented) case Internal.TypeSystem.TypeFlags.FunctionPointer: - throw new NotImplementedException("FunctionPointer signature"); + return new NativeLayoutFunctionPointerTypeSignatureVertexNode(factory, type); default: { @@ -467,6 +468,26 @@ public override Vertex WriteVertex(NodeFactory factory) } } + private sealed class NativeLayoutFunctionPointerTypeSignatureVertexNode : NativeLayoutTypeSignatureVertexNode + { + private readonly NativeLayoutMethodSignatureVertexNode _sig; + + public NativeLayoutFunctionPointerTypeSignatureVertexNode(NodeFactory factory, TypeDesc type) : base(type) + { + _sig = factory.NativeLayout.MethodSignatureVertex(((FunctionPointerType)type).Signature); + } + public override IEnumerable GetStaticDependencies(NodeFactory context) + { + return new DependencyListEntry[] { new DependencyListEntry(_sig, "Method signature") }; + } + public override Vertex WriteVertex(NodeFactory factory) + { + Debug.Assert(Marked, "WriteVertex should only happen for marked vertices"); + + return GetNativeWriter(factory).GetFunctionPointerTypeSignature(_sig.WriteVertex(factory)); + } + } + private sealed class NativeLayoutGenericVarSignatureVertexNode : NativeLayoutTypeSignatureVertexNode { public NativeLayoutGenericVarSignatureVertexNode(TypeDesc type) : base(type) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.NativeLayout.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.NativeLayout.cs index f87f1332b3a81..e98c243b2b726 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.NativeLayout.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.NativeLayout.cs @@ -185,6 +185,20 @@ public IEnumerable TemplateConstructableTypes(TypeDesc type) type = ((ParameterizedType)type).ParameterType; } + if (type.IsFunctionPointer) + { + MethodSignature sig = ((FunctionPointerType)type).Signature; + foreach (var dependency in TemplateConstructableTypes(sig.ReturnType)) + yield return dependency; + + foreach (var param in sig) + foreach (var dependency in TemplateConstructableTypes(param)) + yield return dependency; + + // Nothing else to do for function pointers + yield break; + } + TypeDesc canonicalType = type.ConvertToCanonForm(CanonicalFormKind.Specific); yield return _factory.MaximallyConstructableType(canonicalType); @@ -238,12 +252,6 @@ internal NativeLayoutTypeSignatureVertexNode TypeSignatureVertex(TypeDesc type) type = _factory.TypeSystemContext.GetSignatureVariable(genericParameter.Index, method: (genericParameter.Kind == GenericParameterKind.Method)); } - if (type.Category == TypeFlags.FunctionPointer) - { - // Pretend for now it's an IntPtr, may need to be revisited depending on https://github.com/dotnet/runtime/issues/11354 - type = _factory.TypeSystemContext.GetWellKnownType(WellKnownType.IntPtr); - } - return _typeSignatures.GetOrAdd(type); } diff --git a/src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs b/src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs index 2aa1bd082579f..8e70602728f5a 100644 --- a/src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs +++ b/src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs @@ -55,6 +55,7 @@ internal static int Run() TestMDArrayAddressMethod.Run(); TestNativeLayoutGeneration.Run(); TestByRefLikeVTables.Run(); + TestFunctionPointerLoading.Run(); return 100; } @@ -2434,6 +2435,97 @@ public static void Run() } } + class TestFunctionPointerLoading + { + interface IFace + { + Type GrabManagedFnptr(); + Type GrabManagedRefFnptr(); + Type GrabUnmanagedFnptr(); + Type GrabUnmanagedStdcallFnptr(); + Type GrabUnmanagedStdcallSuppressGCFnptr(); + Array GetFunctionPointerArray(); + Array GetFunctionPointerMdArray(); + Type GrabManagedFnptrOverAlsoGen(); + Type GrabManagedFnptrOverAlsoAlsoGen(); + } + + unsafe class Gen : IFace + { + public Type GrabManagedFnptr() => typeof(delegate*); + public Type GrabManagedRefFnptr() => typeof(delegate*); + public Type GrabUnmanagedFnptr() => typeof(delegate* unmanaged); + public Type GrabUnmanagedStdcallFnptr() => typeof(delegate* unmanaged[Stdcall]); + public Type GrabUnmanagedStdcallSuppressGCFnptr() => typeof(delegate* unmanaged[Stdcall, SuppressGCTransition]); + public Array GetFunctionPointerArray() => new delegate*[1]; + public Array GetFunctionPointerMdArray() => new delegate*[1, 1]; + public Type GrabManagedFnptrOverAlsoGen() => typeof(delegate*, AlsoGen>); + public Type GrabManagedFnptrOverAlsoAlsoGen() => typeof(delegate*, MyGen>); + } + + class MyGen { } + + class AlsoGen { } + + class AlsoAlsoGen { } + + class Atom { } + + static Type s_atomType = typeof(Atom); + + public static void Run() + { + var o = (IFace)Activator.CreateInstance(typeof(Gen<>).MakeGenericType(s_atomType)); + + { + Type t = o.GrabManagedFnptr(); + if (!t.IsFunctionPointer || t.GetFunctionPointerReturnType() != typeof(Atom) || t.IsUnmanagedFunctionPointer) + throw new Exception(); + } + + { + Type t = o.GrabManagedRefFnptr(); + if (!t.IsFunctionPointer || t.GetFunctionPointerReturnType() != typeof(Atom).MakeByRefType() || t.IsUnmanagedFunctionPointer) + throw new Exception(); + } + + { + Type t = o.GrabUnmanagedFnptr(); + if (!t.IsFunctionPointer || t.GetFunctionPointerReturnType() != typeof(Atom) || !t.IsUnmanagedFunctionPointer) + throw new Exception(); + + if (t != o.GrabUnmanagedStdcallFnptr() || t != o.GrabUnmanagedStdcallSuppressGCFnptr()) + throw new Exception(); + } + + { + Array arr = o.GetFunctionPointerArray(); + if (!arr.GetType().GetElementType().IsFunctionPointer) + throw new Exception(); + } + + { + Array arr = o.GetFunctionPointerMdArray(); + if (!arr.GetType().GetElementType().IsFunctionPointer) + throw new Exception(); + } + + { + Type t = o.GrabManagedFnptrOverAlsoGen(); + if (!t.IsFunctionPointer + || t.GetFunctionPointerReturnType().GetGenericTypeDefinition() != typeof(AlsoGen<>) + || t.GetFunctionPointerReturnType().GetGenericArguments()[0] != typeof(Atom)) + throw new Exception(); + } + + { + Type t = o.GrabManagedFnptrOverAlsoAlsoGen(); + if (!t.TypeHandle.Equals(typeof(delegate*, MyGen>).TypeHandle)) + throw new Exception(); + } + } + } + class TestDevirtualization { interface IDevirt