diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicILGenerator.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicILGenerator.cs index b782d0ef86e321..d4fa1049fedde8 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicILGenerator.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicILGenerator.cs @@ -244,6 +244,18 @@ public override void EmitCalli(OpCode opcode, CallingConvention unmanagedCallCon PutInteger4(token); } + /// + public override void EmitCalli(Type functionPointerType) + { + ArgumentNullException.ThrowIfNull(functionPointerType); + + if (!functionPointerType.IsFunctionPointer) + throw new ArgumentException(SR.Argument_MustBeFunctionPointer, nameof(functionPointerType)); + + SignatureHelper sig = SignatureHelper.GetMethodSigHelper(m_scope, functionPointerType); + Emit(OpCodes.Calli, sig); + } + public override void EmitCall(OpCode opcode, MethodInfo methodInfo, Type[]? optionalParameterTypes) { ArgumentNullException.ThrowIfNull(methodInfo); diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/SignatureHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/SignatureHelper.cs index a117d29a3857c3..8a6287f24a5cc7 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/SignatureHelper.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/SignatureHelper.cs @@ -105,6 +105,51 @@ internal static SignatureHelper GetMethodSigHelper(Module? mod, CallingConventio return new SignatureHelper(mod, intCall, returnType, null, null); } + internal static SignatureHelper GetMethodSigHelper(DynamicScope scope, Type functionPointerType) + { + Debug.Assert(functionPointerType.IsFunctionPointer); + + Type retType = functionPointerType.GetFunctionPointerReturnType(); + Type[] retTypeModReqs = retType.GetRequiredCustomModifiers(); + Type[] retTypeModOpts = retType.GetOptionalCustomModifiers(); + Type[] paramTypes = functionPointerType.GetFunctionPointerParameterTypes(); + Type[][] paramModReqs = new Type[paramTypes.Length][]; + Type[][] paramModOpts = new Type[paramTypes.Length][]; + + retType = retType.UnderlyingSystemType; + + for (int i = 0; i < paramTypes.Length; i++) + { + paramModReqs[i] = paramTypes[i].GetRequiredCustomModifiers(); + paramModOpts[i] = paramTypes[i].GetOptionalCustomModifiers(); + paramTypes[i] = paramTypes[i].UnderlyingSystemType; + } + + MdSigCallingConvention callConv = MdSigCallingConvention.Default; + + if (functionPointerType.IsUnmanagedFunctionPointer) + { + callConv = MdSigCallingConvention.Unmanaged; + + if (functionPointerType.GetFunctionPointerCallingConventions() is { Length: 1 } conventions) + { + callConv = conventions[0].FullName switch + { + "System.Runtime.CompilerServices.CallConvCdecl" => MdSigCallingConvention.C, + "System.Runtime.CompilerServices.CallConvStdcall" => MdSigCallingConvention.StdCall, + "System.Runtime.CompilerServices.CallConvThiscall" => MdSigCallingConvention.ThisCall, + "System.Runtime.CompilerServices.CallConvFastcall" => MdSigCallingConvention.FastCall, + _ => MdSigCallingConvention.Unmanaged + }; + } + } + + SignatureHelper sig = new(null, callConv); + sig.AddDynamicArgument(scope, retType, retTypeModReqs, retTypeModOpts, false); + sig.AddArguments(scope, paramTypes, paramModReqs, paramModOpts); + return sig; + } + public static SignatureHelper GetLocalVarSigHelper() { return GetLocalVarSigHelper(null); @@ -320,8 +365,8 @@ private void AddOneArgTypeHelper(Type clsArgument, Type[]? requiredCustomModifie AddOneArgTypeHelper(clsArgument); } - private void AddOneArgTypeHelper(Type clsArgument) { AddOneArgTypeHelperWorker(clsArgument, false); } - private void AddOneArgTypeHelperWorker(Type clsArgument, bool lastWasGenericInst) + private void AddOneArgTypeHelper(Type clsArgument, DynamicScope? scope = null) { AddOneArgTypeHelperWorker(clsArgument, false, scope); } + private void AddOneArgTypeHelperWorker(Type clsArgument, bool lastWasGenericInst, DynamicScope? scope) { if (clsArgument.IsGenericParameter) { @@ -336,7 +381,7 @@ private void AddOneArgTypeHelperWorker(Type clsArgument, bool lastWasGenericInst { AddElementType(CorElementType.ELEMENT_TYPE_GENERICINST); - AddOneArgTypeHelperWorker(clsArgument.GetGenericTypeDefinition(), true); + AddOneArgTypeHelperWorker(clsArgument.GetGenericTypeDefinition(), true, scope); Type[] args = clsArgument.GetGenericArguments(); @@ -424,6 +469,25 @@ private void AddOneArgTypeHelperWorker(Type clsArgument, bool lastWasGenericInst AddData(0); } } + else if (clsArgument.IsFunctionPointer) + { + if (scope == null) + throw new NotSupportedException(SR.NotSupported_FunctionPointerSignature); + + AddData((int)CorElementType.ELEMENT_TYPE_FNPTR); + SignatureHelper sig = GetMethodSigHelper(scope, clsArgument); + byte[] bytes = sig.GetSignature(); + + if (m_currSig + bytes.Length > m_signature.Length) + { + m_signature = ExpandArray(m_signature, m_signature.Length + bytes.Length); + } + + for (int i = 0; i < bytes.Length; i++) + { + m_signature[m_currSig++] = bytes[i]; + } + } else { CorElementType type = CorElementType.ELEMENT_TYPE_MAX; @@ -729,9 +793,10 @@ internal byte[] InternalGetSignatureArray() return temp; } - internal void AddDynamicArgument(DynamicScope dynamicScope, Type clsArgument, Type[]? requiredCustomModifiers, Type[]? optionalCustomModifiers) + internal void AddDynamicArgument(DynamicScope dynamicScope, Type clsArgument, Type[]? requiredCustomModifiers, Type[]? optionalCustomModifiers, bool incrementArgCount = true) { - IncrementArgCounts(); + if (incrementArgCount) + IncrementArgCounts(); Debug.Assert(clsArgument != null); @@ -750,6 +815,9 @@ internal void AddDynamicArgument(DynamicScope dynamicScope, Type clsArgument, Ty if (t.ContainsGenericParameters) throw new ArgumentException(SR.Argument_GenericsInvalid, nameof(optionalCustomModifiers)); + if (t.IsFunctionPointer) + throw new ArgumentException(SR.Argument_FunctionPointersInvalid, nameof(optionalCustomModifiers)); + AddElementType(CorElementType.ELEMENT_TYPE_CMOD_OPT); int token = dynamicScope.GetTokenFor(rtType.TypeHandle); @@ -773,6 +841,9 @@ internal void AddDynamicArgument(DynamicScope dynamicScope, Type clsArgument, Ty if (t.ContainsGenericParameters) throw new ArgumentException(SR.Argument_GenericsInvalid, nameof(requiredCustomModifiers)); + if (t.IsFunctionPointer) + throw new ArgumentException(SR.Argument_FunctionPointersInvalid, nameof(requiredCustomModifiers)); + AddElementType(CorElementType.ELEMENT_TYPE_CMOD_REQD); int token = dynamicScope.GetTokenFor(rtType.TypeHandle); @@ -781,7 +852,18 @@ internal void AddDynamicArgument(DynamicScope dynamicScope, Type clsArgument, Ty } } - AddOneArgTypeHelper(clsArgument); + AddOneArgTypeHelper(clsArgument, dynamicScope); + } + + internal void AddArguments(DynamicScope dynamicScope, Type[]? arguments, Type[][]? requiredCustomModifiers, Type[][]? optionalCustomModifiers) + { + if (arguments is null) + return; + + for (int i = 0; i < arguments.Length; i++) + { + AddDynamicArgument(dynamicScope, arguments[i], requiredCustomModifiers?[i], optionalCustomModifiers?[i]); + } } #endregion diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index 4115a1d3c05a4d..2b170e8affabc9 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -813,6 +813,29 @@ internal RuntimeType MakeByRef() return type!; } + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeTypeHandle_MakeFunctionPointer")] + private static partial void MakeFunctionPointer(nint* retAndParamTypes, int numArgs, [MarshalAs(UnmanagedType.Bool)] bool isUnmanaged, ObjectHandleOnStack type); + + internal RuntimeType MakeFunctionPointer(Type[] parameterTypes, bool isUnmanaged) + { + int count = 1 + parameterTypes.Length; + nint[] retAndParamTypeHandles = new nint[count]; + + retAndParamTypeHandles[0] = GetNativeHandle().Value; + for (int i = 0; i < parameterTypes.Length; i++) + retAndParamTypeHandles[i + 1] = parameterTypes[i].TypeHandle.Value; + + RuntimeType? type = null; + fixed (nint* pRetAndParamTypeHandles = retAndParamTypeHandles) + { + MakeFunctionPointer(pRetAndParamTypeHandles, parameterTypes.Length, isUnmanaged, ObjectHandleOnStack.Create(ref type)); + } + + GC.KeepAlive(m_type); + GC.KeepAlive(parameterTypes); + return type!; + } + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeTypeHandle_MakePointer")] private static partial void MakePointer(QCallTypeHandle handle, ObjectHandleOnStack type); diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index 764599e2aae6dc..4fe4d8f0de7361 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -3729,6 +3729,24 @@ public override Type MakeArrayType(int rank) return new RuntimeTypeHandle(this).MakeArray(rank); } + public override Type MakeFunctionPointerType(Type[]? parameterTypes, bool isUnmanaged = false) + { + parameterTypes = (parameterTypes != null) ? (Type[])parameterTypes.Clone() : []; + for (int i = 0; i < parameterTypes.Length; i++) + { + Type paramType = parameterTypes[i]; + ArgumentNullException.ThrowIfNull(paramType, nameof(parameterTypes)); + + if (paramType is not RuntimeType) + return Type.MakeFunctionPointerSignatureType(this, parameterTypes, isUnmanaged); + + if (paramType == typeof(void) || paramType.IsGenericTypeDefinition) + throw new ArgumentException(string.Format(SR.FunctionPointer_ParameterInvalid, paramType.ToString()), nameof(parameterTypes)); + } + + return new RuntimeTypeHandle(this).MakeFunctionPointer(parameterTypes, isUnmanaged); + } + public override StructLayoutAttribute? StructLayoutAttribute => PseudoCustomAttribute.GetStructLayoutCustomAttribute(this); #endregion diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/TypeUnifier.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/TypeUnifier.cs index 7cd2e9588f5125..c75c2007535aef 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/TypeUnifier.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/TypeUnifier.cs @@ -57,6 +57,11 @@ public static RuntimeTypeInfo GetPointerType(this RuntimeTypeInfo targetType) return RuntimePointerTypeInfo.GetPointerTypeInfo(targetType); } + public static RuntimeTypeInfo GetFunctionPointerType(this RuntimeTypeInfo returnType, RuntimeTypeInfo[] parameterTypes, bool isUnmanaged) + { + return RuntimeFunctionPointerTypeInfo.GetFunctionPointerTypeInfo(returnType, parameterTypes, isUnmanaged); + } + public static RuntimeTypeInfo GetConstructedGenericTypeNoConstraintCheck(this RuntimeTypeInfo genericTypeDefinition, RuntimeTypeInfo[] genericTypeArguments) { return RuntimeConstructedGenericTypeInfo.GetRuntimeConstructedGenericTypeInfoNoConstraintCheck(genericTypeDefinition, genericTypeArguments); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeTypeInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeTypeInfo.cs index da86ebb4499cf1..8fd54ee8f44c92 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeTypeInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeTypeInfo.cs @@ -414,6 +414,28 @@ public Type MakeArrayType(int rank) return this.GetMultiDimArrayType(rank).ToType(); } + public Type MakeFunctionPointerType(Type[]? parameterTypes, bool isUnmanaged = false) + { + parameterTypes ??= []; + RuntimeTypeInfo[] runtimeParameterTypes = new RuntimeTypeInfo[parameterTypes.Length]; + + for (int i = 0; i < parameterTypes.Length; i++) + { + Type paramType = parameterTypes[i]; + ArgumentNullException.ThrowIfNull(paramType, nameof(parameterTypes)); + + if (paramType is not RuntimeType rtType) + return Type.MakeFunctionPointerSignatureType(this.ToType(), parameterTypes, isUnmanaged); + + if (rtType == typeof(void) || rtType.IsGenericTypeDefinition) + throw new ArgumentException(string.Format(SR.FunctionPointer_ParameterInvalid, rtType.ToString()), nameof(parameterTypes)); + + runtimeParameterTypes[i] = rtType.GetRuntimeTypeInfo(); + } + + return this.GetFunctionPointerType(runtimeParameterTypes, isUnmanaged).ToType(); + } + public Type MakePointerType() { return this.GetPointerType().ToType(); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeType.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeType.NativeAot.cs index 0163a849c521cd..a0285f166c0eca 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeType.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeType.NativeAot.cs @@ -856,6 +856,9 @@ public override Type MakeArrayType() public override Type MakeArrayType(int rank) => GetRuntimeTypeInfo().MakeArrayType(rank); + public override Type MakeFunctionPointerType(Type[]? parameterTypes, bool isUnmanaged = false) + => GetRuntimeTypeInfo().MakeFunctionPointerType(parameterTypes, isUnmanaged); + [RequiresDynamicCode("The native code for this instantiation might not be available at runtime.")] [RequiresUnreferencedCode("If some of the generic arguments are annotated (either with DynamicallyAccessedMembersAttribute, or generic constraints), trimming can't validate that the requirements of those annotations are met.")] public override Type MakeGenericType(params Type[] instantiation) diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 0a03fbce6b82a2..2c1c69675d4df5 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -132,6 +132,7 @@ static const Entry s_QCall[] = DllImportEntry(RuntimeTypeHandle_MakeByRef) DllImportEntry(RuntimeTypeHandle_MakeSZArray) DllImportEntry(RuntimeTypeHandle_MakeArray) + DllImportEntry(RuntimeTypeHandle_MakeFunctionPointer) DllImportEntry(RuntimeTypeHandle_GetConstraints) DllImportEntry(RuntimeTypeHandle_GetArgumentTypesFromFunctionPointer) DllImportEntry(RuntimeTypeHandle_GetAssemblySlow) diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index 3cb83744e9e03b..0d9fc007142f2c 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -1091,6 +1091,22 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_MakeByRef(QCall::TypeHandle pTypeHan return; } +extern "C" void QCALLTYPE RuntimeTypeHandle_MakeFunctionPointer(TypeHandle* pRetAndArgTypes, INT32 numArgs, BOOL isUnmanaged, QCall::ObjectHandleOnStack retType) +{ + QCALL_CONTRACT; + + TypeHandle fnPtrHandle; + + BEGIN_QCALL; + BYTE callConv = (BYTE)(isUnmanaged ? IMAGE_CEE_CS_CALLCONV_UNMANAGED : IMAGE_CEE_CS_CALLCONV_DEFAULT); + fnPtrHandle = ClassLoader::LoadFnptrTypeThrowing(callConv, numArgs, pRetAndArgTypes); + GCX_COOP(); + retType.Set(fnPtrHandle.GetManagedClassObject()); + END_QCALL; + + return; +} + extern "C" void QCALLTYPE RuntimeTypeHandle_Instantiate(QCall::TypeHandle pTypeHandle, TypeHandle * pInstArray, INT32 cInstArray, QCall::ObjectHandleOnStack retType) { QCALL_CONTRACT; diff --git a/src/coreclr/vm/runtimehandles.h b/src/coreclr/vm/runtimehandles.h index fb3bbf2b475c41..de19a93f279f55 100644 --- a/src/coreclr/vm/runtimehandles.h +++ b/src/coreclr/vm/runtimehandles.h @@ -160,6 +160,7 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_MakeByRef(QCall::TypeHandle pTypeHan extern "C" void QCALLTYPE RuntimeTypeHandle_MakePointer(QCall::TypeHandle pTypeHandle, QCall::ObjectHandleOnStack retType); extern "C" void QCALLTYPE RuntimeTypeHandle_MakeSZArray(QCall::TypeHandle pTypeHandle, QCall::ObjectHandleOnStack retType); extern "C" void QCALLTYPE RuntimeTypeHandle_MakeArray(QCall::TypeHandle pTypeHandle, INT32 rank, QCall::ObjectHandleOnStack retType); +extern "C" void QCALLTYPE RuntimeTypeHandle_MakeFunctionPointer(TypeHandle* pRetAndArgTypes, INT32 numArgs, BOOL isUnmanaged, QCall::ObjectHandleOnStack retType); extern "C" void QCALLTYPE RuntimeTypeHandle_PrepareMemberInfoCache(QCall::TypeHandle pMemberInfoCache); extern "C" void QCALLTYPE RuntimeTypeHandle_ConstructName(QCall::TypeHandle pTypeHandle, DWORD format, QCall::StringHandleOnStack retString); extern "C" void QCALLTYPE RuntimeTypeHandle_GetInterfaces(MethodTable* pMT, QCall::ObjectHandleOnStack result); diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index f511ebeb21810d..3f25e87d269c4b 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -4441,4 +4441,19 @@ The directory '{0}' does not have the expected owner permissions: {1}. + + Argument must represent a function pointer type. + + + Unmanaged calling conventions cannot be specified for managed function pointers. + + + '{0}' cannot be used as a parameter type for a function pointer. + + + Function pointer signature can only be encoded by dynamic ILGenerator. + + + Function pointer types are not valid. + diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 56fdc095a74eed..bdae2abd118f5d 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -778,9 +778,11 @@ + + @@ -2938,4 +2940,4 @@ - \ No newline at end of file + diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/EnumBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/EnumBuilder.cs index e013510453c281..b8b250fb8507b3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/EnumBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/EnumBuilder.cs @@ -62,5 +62,10 @@ public override Type MakeArrayType(int rank) string s = GetRankString(rank); return SymbolType.FormCompoundType(s, this, 0)!; } + + public override Type MakeFunctionPointerType(Type[]? parameterTypes, bool isUnmanaged = false) + { + return Type.MakeFunctionPointerSignatureType(this, parameterTypes, isUnmanaged); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ILGenerator.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ILGenerator.cs index fb0703fe37d9a7..8342c98bb6deb9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ILGenerator.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ILGenerator.cs @@ -44,6 +44,16 @@ public abstract void EmitCalli(OpCode opcode, CallingConventions callingConventi public abstract void EmitCalli(OpCode opcode, CallingConvention unmanagedCallConv, Type? returnType, Type[]? parameterTypes); + /// + /// Puts a instruction onto the Microsoft intermediate language (MSIL) stream, + /// specifying the type of the function pointer to indirectly call. + /// + /// + /// The type of the function pointer to indirectly call. + /// The specified type must represent a function pointer type. + /// + public virtual void EmitCalli(Type functionPointerType) => throw new NotSupportedException(SR.NotSupported_SubclassOverride); + public abstract void EmitCall(OpCode opcode, MethodInfo methodInfo, Type[]? optionalParameterTypes); public abstract void Emit(OpCode opcode, SignatureHelper signature); diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/TypeBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/TypeBuilder.cs index aaf64eeb3ceebe..b1a0c16f804bd4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/TypeBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/TypeBuilder.cs @@ -321,6 +321,11 @@ public override Type MakeGenericType(params Type[] typeArguments) return TypeBuilderInstantiation.MakeGenericType(this, typeArguments); } + public override Type MakeFunctionPointerType(Type[]? parameterTypes, bool isUnmanaged = false) + { + return Type.MakeFunctionPointerSignatureType(this, parameterTypes, isUnmanaged); + } + #region Public Static Methods [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2055:UnrecognizedReflectionPattern", Justification = "MakeGenericType is only called on a TypeBuilder which is not subject to trimming")] diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/SignatureFunctionPointerType.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/SignatureFunctionPointerType.cs new file mode 100644 index 00000000000000..0d4e5c9c732d7a --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/SignatureFunctionPointerType.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text; + +namespace System.Reflection +{ + internal sealed class SignatureFunctionPointerType : SignatureType + { + internal SignatureFunctionPointerType(Type returnType, Type[] parameterTypes, bool isUnmanaged, Type[] callingConventions) + { + _returnType = returnType; + _parameterTypes = parameterTypes; + _isUnmanaged = isUnmanaged; + _callingConventions = callingConventions; + } + + private readonly Type _returnType; + private readonly Type[] _parameterTypes; + private readonly bool _isUnmanaged; + private readonly Type[] _callingConventions; + + public override bool IsFunctionPointer => true; + public override bool IsUnmanagedFunctionPointer => _isUnmanaged; + + public override Type[] GetFunctionPointerCallingConventions() => (Type[])_callingConventions.Clone(); + public override Type[] GetFunctionPointerParameterTypes() => (Type[])_parameterTypes.Clone(); + public override Type GetFunctionPointerReturnType() => _returnType; + + public override bool IsEnum => false; + public override bool IsTypeDefinition => false; + public override bool IsSZArray => false; + public override bool IsVariableBoundArray => false; + public override bool IsByRefLike => false; + public override bool IsGenericTypeDefinition => false; + public override bool IsConstructedGenericType => false; + public override bool IsGenericParameter => false; + public override bool IsGenericTypeParameter => false; + public override bool IsGenericMethodParameter => false; + public override bool ContainsGenericParameters + { + get + { + if (_returnType.ContainsGenericParameters) + { + return true; + } + + for (int i = 0; i < _parameterTypes.Length; i++) + { + if (_parameterTypes[i].ContainsGenericParameters) + { + return true; + } + } + + return false; + } + } + public override Type[] GenericTypeArguments => []; + public override int GenericParameterPosition => 0; + internal override SignatureType? ElementType => null; + public override string Name => string.Empty; + public override string? Namespace => null; + protected override bool HasElementTypeImpl() => false; + protected override bool IsArrayImpl() => false; + protected override bool IsByRefImpl() => false; + protected override bool IsPointerImpl() => false; + public sealed override int GetArrayRank() => throw new ArgumentException(SR.Argument_HasToBeArrayClass); + public sealed override Type GetGenericTypeDefinition() => throw new InvalidOperationException(SR.InvalidOperation_NotGenericType); + public override Type[] GetGenericArguments() => []; + public override Type[] GetOptionalCustomModifiers() => []; + public override Type[] GetRequiredCustomModifiers() => []; + protected override bool IsValueTypeImpl() => false; + + public override string ToString() + { + StringBuilder sb = new(); + sb.Append(_returnType.ToString()); + sb.Append('('); + + for (int i = 0; i < _parameterTypes.Length; i++) + { + sb.Append(_parameterTypes[i].ToString()); + if (i < _parameterTypes.Length - 1) + sb.Append(", "); + } + + sb.Append(')'); + return sb.ToString(); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/SignatureModifiedType.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/SignatureModifiedType.cs new file mode 100644 index 00000000000000..799e025506bbbb --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/SignatureModifiedType.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Reflection +{ + internal sealed class SignatureModifiedType : SignatureType + { + internal SignatureModifiedType(Type baseType, Type[] requiredCustomModifiers, Type[] optionalCustomModifiers) + { + if (baseType is SignatureModifiedType modifiedType) + { + baseType = modifiedType.UnderlyingSystemType; + requiredCustomModifiers = [.. modifiedType.GetRequiredCustomModifiers(), .. requiredCustomModifiers]; + optionalCustomModifiers = [.. modifiedType.GetOptionalCustomModifiers(), .. optionalCustomModifiers]; + } + + _unmodifiedType = baseType; + _requiredCustomModifiers = requiredCustomModifiers; + _optionalCustomModifiers = optionalCustomModifiers; + } + + private readonly Type _unmodifiedType; + private readonly Type[] _requiredCustomModifiers; + private readonly Type[] _optionalCustomModifiers; + + public override Type UnderlyingSystemType => _unmodifiedType; + public override Type[] GetRequiredCustomModifiers() => (Type[])_requiredCustomModifiers.Clone(); + public override Type[] GetOptionalCustomModifiers() => (Type[])_optionalCustomModifiers.Clone(); + + public override bool IsTypeDefinition => _unmodifiedType.IsTypeDefinition; + public override bool IsSZArray => _unmodifiedType.IsSZArray; + public override bool IsVariableBoundArray => _unmodifiedType.IsVariableBoundArray; + public override bool IsByRefLike => _unmodifiedType.IsByRefLike; + public override bool IsFunctionPointer => _unmodifiedType.IsFunctionPointer; + public override bool IsGenericTypeDefinition => _unmodifiedType.IsGenericTypeDefinition; + public override bool IsConstructedGenericType => _unmodifiedType.IsConstructedGenericType; + public override bool IsGenericParameter => _unmodifiedType.IsGenericParameter; + public override bool IsGenericTypeParameter => _unmodifiedType.IsGenericTypeParameter; + public override bool IsGenericMethodParameter => _unmodifiedType.IsGenericMethodParameter; + public override bool IsUnmanagedFunctionPointer => _unmodifiedType.IsUnmanagedFunctionPointer; + public override bool ContainsGenericParameters => _unmodifiedType.ContainsGenericParameters; + public override Type[] GenericTypeArguments => _unmodifiedType.GenericTypeArguments; + public override int GenericParameterPosition => _unmodifiedType.GenericParameterPosition; + internal override SignatureType? ElementType => HasElementType ? new SignatureModifiedType(_unmodifiedType.GetElementType()!, [], []) : null; + public override string Name => _unmodifiedType.Name; + public override string? Namespace => _unmodifiedType.Namespace; + public override bool IsEnum => _unmodifiedType.IsEnum; + protected override bool HasElementTypeImpl() => _unmodifiedType.HasElementType; + protected override bool IsArrayImpl() => _unmodifiedType.IsArray; + protected override bool IsByRefImpl() => _unmodifiedType.IsByRef; + protected override bool IsPointerImpl() => _unmodifiedType.IsPointer; + public override int GetArrayRank() => _unmodifiedType.GetArrayRank(); + public override Type[] GetFunctionPointerCallingConventions() => _unmodifiedType.GetFunctionPointerCallingConventions(); + public override Type[] GetFunctionPointerParameterTypes() => _unmodifiedType.GetFunctionPointerParameterTypes(); + public override Type GetFunctionPointerReturnType() => _unmodifiedType.GetFunctionPointerReturnType(); + public override Type GetGenericTypeDefinition() => _unmodifiedType.GetGenericTypeDefinition(); + public override Type[] GetGenericArguments() => _unmodifiedType.GetGenericArguments(); + public override string ToString() => _unmodifiedType.ToString(); + protected override bool IsValueTypeImpl() => _unmodifiedType.IsValueType; + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/SignatureType.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/SignatureType.cs index 90832ed5f1711b..a29ac0441f48d7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/SignatureType.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/SignatureType.cs @@ -48,6 +48,7 @@ public sealed override Type MakeArrayType(int rank) } public sealed override Type MakeByRefType() => new SignatureByRefType(this); public sealed override Type MakePointerType() => new SignaturePointerType(this); + public override Type MakeFunctionPointerType(Type[]? parameterTypes, bool isUnmanaged = false) => Type.MakeFunctionPointerSignatureType(this, parameterTypes, isUnmanaged); [RequiresDynamicCode("The native code for this instantiation might not be available at runtime.")] [RequiresUnreferencedCode("If some of the generic arguments are annotated (either with DynamicallyAccessedMembersAttribute, or generic constraints), trimming can't validate that the requirements of those annotations are met.")] @@ -68,7 +69,7 @@ public sealed override Type MakeArrayType(int rank) public sealed override bool Equals(Type? o) => base.Equals(o); public sealed override int GetHashCode() => base.GetHashCode(); #endif - public sealed override Type UnderlyingSystemType => this; // Equals(Type) depends on this. + public override Type UnderlyingSystemType => this; // Equals(Type) depends on this. // Naming and diagnostics public abstract override string Name { get; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Type.cs b/src/libraries/System.Private.CoreLib/src/System/Type.cs index 61ce2deace997a..c9e9ab23dffb1f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Type.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Type.cs @@ -645,10 +645,135 @@ public virtual Array GetEnumValues() public virtual Type MakeArrayType(int rank) => throw new NotSupportedException(); public virtual Type MakeByRefType() => throw new NotSupportedException(); + /// + /// Creates a function pointer signature type with the specified return type, parameter types and calling conventions. + /// + /// The return type of the function pointer. + /// An array of types representing the parameters of the function pointer. + /// if the function pointer uses unmanaged calling conventions; otherwise, . + /// An array of types representing the calling conventions applied to the function pointer. + /// A object representing the constructed function pointer signature. + /// + /// Thrown when , any supplied parameter type + /// or any supplied calling convention is . + /// + public static Type MakeFunctionPointerSignatureType(Type returnType, Type[]? parameterTypes, bool isUnmanaged = false, Type[]? callingConventions = null) + { + ArgumentNullException.ThrowIfNull(returnType); + parameterTypes = (parameterTypes != null) ? (Type[])parameterTypes.Clone() : []; + callingConventions = (callingConventions != null) ? (Type[])callingConventions.Clone() : []; + + for (int i = 0; i < parameterTypes.Length; i++) + { + Type paramType = parameterTypes[i]; + ArgumentNullException.ThrowIfNull(paramType, nameof(parameterTypes)); + + if (paramType == typeof(void) || paramType.IsGenericTypeDefinition) + throw new ArgumentException(string.Format(SR.FunctionPointer_ParameterInvalid, paramType.ToString()), nameof(parameterTypes)); + } + + for (int i = 0; i < callingConventions.Length; i++) + ArgumentNullException.ThrowIfNull(callingConventions[i], nameof(callingConventions)); + + if (!isUnmanaged && callingConventions.Length >= 1) + throw new ArgumentException(SR.ManagedFunctionPointer_CallingConventionsNotAllowed, nameof(callingConventions)); + + // Reverse calling conventions for consistency with Roslyn: + // delegate* unmanaged[Cdecl, MemberFunction] => int32 modopt(CallConvMemberFunction) modopt(CallConvCdecl) *() + Array.Reverse(callingConventions); + + bool builtInCallConv = false; + if (callingConventions.Length == 1) + { + // Known calling conventions with direct IL equivalent + string? callConv = callingConventions[0].FullName; + if (!string.IsNullOrEmpty(callConv) && + (callConv == "System.Runtime.CompilerServices.CallConvCdecl" + || callConv == "System.Runtime.CompilerServices.CallConvStdcall" + || callConv == "System.Runtime.CompilerServices.CallConvThiscall" + || callConv == "System.Runtime.CompilerServices.CallConvFastcall")) + builtInCallConv = true; + } + + if (isUnmanaged && !builtInCallConv && callingConventions.Length > 0) + { + // Newer or multiple calling conventions specified -> encoded as modopts + returnType = MakeModifiedSignatureType( + returnType, + requiredCustomModifiers: [], + optionalCustomModifiers: callingConventions); + } + + return new SignatureFunctionPointerType( + returnType, + parameterTypes, + isUnmanaged, + callingConventions); + } + + /// + /// Creates a object that represents a function pointer type + /// with the specified parameter types. The return type is represented by the + /// current instance. + /// + /// An array of objects that represent the parameter types of + /// the function pointer. If , an empty parameter list + /// is assumed. + /// + /// + /// to create an unmanaged function pointer type; otherwise, + /// to create a managed function pointer type. + /// + /// + /// A object that represents the function pointer type whose + /// return type is the current and whose parameter types are + /// specified by . + /// + public virtual Type MakeFunctionPointerType(Type[]? parameterTypes, bool isUnmanaged = false) => throw new NotSupportedException(SR.NotSupported_SubclassOverride); + [RequiresDynamicCode("The native code for this instantiation might not be available at runtime.")] [RequiresUnreferencedCode("If some of the generic arguments are annotated (either with DynamicallyAccessedMembersAttribute, or generic constraints), trimming can't validate that the requirements of those annotations are met.")] public virtual Type MakeGenericType(params Type[] typeArguments) => throw new NotSupportedException(SR.NotSupported_SubclassOverride); + /// + /// Creates a that represents with the specified + /// required and optional custom modifiers applied to its signature. + /// + /// The to modify. This parameter cannot be . + /// + /// An array of objects that represent required custom modifiers + /// (modreq) to apply to the returned type. + /// If , no required custom modifiers are applied. + /// + /// + /// An array of objects that represent optional custom modifiers + /// (modopt) to apply to the returned type. + /// If , no optional custom modifiers are applied. + /// + /// A new instance that represents the specified + /// with the given required and optional custom modifiers. + /// + /// Thrown when + /// or any specified modifier type is . + /// + public static Type MakeModifiedSignatureType(Type type, Type[]? requiredCustomModifiers, Type[]? optionalCustomModifiers) + { + ArgumentNullException.ThrowIfNull(type); + requiredCustomModifiers = (requiredCustomModifiers != null) ? (Type[])requiredCustomModifiers.Clone() : []; + optionalCustomModifiers = (optionalCustomModifiers != null) ? (Type[])optionalCustomModifiers.Clone() : []; + + for (int i = 0; i < requiredCustomModifiers.Length; i++) + ArgumentNullException.ThrowIfNull(requiredCustomModifiers[i], nameof(requiredCustomModifiers)); + + for (int i = 0; i < optionalCustomModifiers.Length; i++) + ArgumentNullException.ThrowIfNull(optionalCustomModifiers[i], nameof(optionalCustomModifiers)); + + return new SignatureModifiedType( + type, + requiredCustomModifiers, + optionalCustomModifiers); + } + public virtual Type MakePointerType() => throw new NotSupportedException(); public static Type MakeGenericSignatureType(Type genericTypeDefinition, params Type[] typeArguments) diff --git a/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.cs b/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.cs index c5dcbd65fb1888..747b82c32b4eb8 100644 --- a/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.cs +++ b/src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.cs @@ -48,6 +48,7 @@ public void Emit(System.Reflection.Emit.OpCode opcode, sbyte arg) { } public abstract void EmitCall(System.Reflection.Emit.OpCode opcode, System.Reflection.MethodInfo methodInfo, System.Type[]? optionalParameterTypes); public abstract void EmitCalli(System.Reflection.Emit.OpCode opcode, System.Reflection.CallingConventions callingConvention, System.Type? returnType, System.Type[]? parameterTypes, System.Type[]? optionalParameterTypes); public abstract void EmitCalli(System.Reflection.Emit.OpCode opcode, System.Runtime.InteropServices.CallingConvention unmanagedCallConv, System.Type? returnType, System.Type[]? parameterTypes); + public virtual void EmitCalli(System.Type functionPointerType) { } public virtual void EmitWriteLine(System.Reflection.Emit.LocalBuilder localBuilder) { } public virtual void EmitWriteLine(System.Reflection.FieldInfo fld) { } public virtual void EmitWriteLine(string value) { } diff --git a/src/libraries/System.Reflection.Emit.ILGeneration/tests/ILGenerator/Emit4Tests.cs b/src/libraries/System.Reflection.Emit.ILGeneration/tests/ILGenerator/Emit4Tests.cs index ec38955ac9b4ae..b75edf40756c33 100644 --- a/src/libraries/System.Reflection.Emit.ILGeneration/tests/ILGenerator/Emit4Tests.cs +++ b/src/libraries/System.Reflection.Emit.ILGeneration/tests/ILGenerator/Emit4Tests.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Xunit; @@ -170,6 +172,116 @@ public void TestDynamicMethodEmitCalliNonBlittable() Assert.Equal(result, resultValue); } + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/124149", TestRuntimes.Mono)] + public void TestDynamicMethodEmitCalliFnPtrManaged() + { + Type fnPtrType = Type.MakeFunctionPointerSignatureType(typeof(int), [typeof(int), typeof(int)]); + DynamicMethod dynamicMethod = new("Test", typeof(int), [typeof(nint)]); + + ILGenerator il = dynamicMethod.GetILGenerator(); + il.Emit(OpCodes.Ldc_I4_S, (byte)10); + il.Emit(OpCodes.Ldc_I4_S, (byte)5); + il.Emit(OpCodes.Ldarg_0); + il.EmitCalli(fnPtrType); + il.Emit(OpCodes.Ret); + + object resultValue = dynamicMethod.Invoke(null, [((Func)Int32Sum).Method.MethodHandle.GetFunctionPointer()]); + + Assert.Equal(15, resultValue); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/124149", TestRuntimes.Mono)] + public void TestDynamicMethodEmitCalliFnPtrStdCall() + { + int a = 1, b = 1, result = 2; + + Type returnType = typeof(int); + + var dynamicMethod = new DynamicMethod("F", returnType, [typeof(nint), typeof(int), typeof(int)]); + + ILGenerator il = dynamicMethod.GetILGenerator(); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Ldarg_2); + il.Emit(OpCodes.Ldarg_0); + il.EmitCalli(Type.MakeFunctionPointerSignatureType( + returnType, + parameterTypes: [typeof(int), typeof(int)], + isUnmanaged: true, + callingConventions: [typeof(CallConvStdcall)])); + il.Emit(OpCodes.Ret); + + var del = new Int32SumStdCall(Int32Sum); + IntPtr funcPtr = Marshal.GetFunctionPointerForDelegate(del); + + object resultValue = dynamicMethod + .Invoke(null, [funcPtr, a, b]); + + GC.KeepAlive(del); + + Assert.IsType(returnType, resultValue); + Assert.Equal(result, resultValue); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/124149", TestRuntimes.Mono)] + public unsafe void TestDynamicMethodEmitCalli_NestedFunctionPointer() + { + // delegate*, int> + Type type = Type.MakeFunctionPointerSignatureType( + typeof(int), + [Type.MakeFunctionPointerSignatureType(typeof(bool), [typeof(short)], true, [typeof(CallConvStdcall), typeof(CallConvMemberFunction)])]); + + DynamicMethod dynamicMethod = new("F", typeof(int), [typeof(nint)]); + ILGenerator il = dynamicMethod.GetILGenerator(); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Conv_I); + il.Emit(OpCodes.Ldarg_0); + il.EmitCalli(type); + il.Emit(OpCodes.Ret); + + object result = dynamicMethod.Invoke(null, [(nint)(delegate*, int>)(&ComplexSignature)]); + Assert.Equal(5, (int)result); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/124149", TestRuntimes.Mono)] + public unsafe void TestDynamicMethodEmitCalli_NestedFunctionPointer2() + { + // delegate**>>>, delegate*, int>> + Type type = Type.MakeFunctionPointerSignatureType( + Type.MakeFunctionPointerSignatureType( + typeof(int), + [Type.MakeFunctionPointerSignatureType( + typeof(bool), + [typeof(short)], true, + [typeof(CallConvStdcall), typeof(CallConvMemberFunction)])], false), + [Type.MakeFunctionPointerSignatureType( + Type.MakeFunctionPointerSignatureType( + Type.MakeFunctionPointerSignatureType( + typeof(List<>).MakeGenericType(typeof(short)).MakePointerType(), + [], true, + [typeof(CallConvFastcall), typeof(CallConvSuppressGCTransition)]), + [], false), + [], true, + [typeof(CallConvCdecl)])]); + + DynamicMethod dynamicMethod = new("F", typeof(nint), [typeof(nint)]); + ILGenerator il = dynamicMethod.GetILGenerator(); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Conv_I); + il.Emit(OpCodes.Ldarg_0); + il.EmitCalli(type); + il.Emit(OpCodes.Ret); + + object result = dynamicMethod.Invoke( + null, + [(nint)(delegate**>>>, + delegate*, int>>)(&EvenMoreComplexSignature)]); + Assert.Equal((nint)(delegate*, int>)(&ComplexSignature), (nint)result); + } + [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int Int32SumStdCall(int a, int b); @@ -179,5 +291,10 @@ public void TestDynamicMethodEmitCalliNonBlittable() private static int Int32Sum(int a, int b) => a + b; private static string StringReverse(string a) => string.Join("", a.Reverse()); + + private unsafe static int ComplexSignature(delegate* unmanaged[Stdcall, MemberFunction] arg) => 5; + + private unsafe static delegate*, int> EvenMoreComplexSignature( + delegate* unmanaged[Cdecl]*>>> arg) => &ComplexSignature; } } diff --git a/src/libraries/System.Reflection.Emit.ILGeneration/tests/SignatureHelper/SignatureHelperGetMethodSigHelper.cs b/src/libraries/System.Reflection.Emit.ILGeneration/tests/SignatureHelper/SignatureHelperGetMethodSigHelper.cs index b6c289f95f005b..2ba72bf58cfa99 100644 --- a/src/libraries/System.Reflection.Emit.ILGeneration/tests/SignatureHelper/SignatureHelperGetMethodSigHelper.cs +++ b/src/libraries/System.Reflection.Emit.ILGeneration/tests/SignatureHelper/SignatureHelperGetMethodSigHelper.cs @@ -56,5 +56,12 @@ public void GetMethodSigHelper_Module_Type_TypeArray_NullObjectInParameterType_T ModuleBuilder module = Helpers.DynamicModule(); AssertExtensions.Throws("argument", () => SignatureHelper.GetMethodSigHelper(module, typeof(string), new Type[] { typeof(char), null })); } + + [Fact] + public void GetMethodSigHelper_FunctionPointerParameter_ThrowsNotSupportedException() + { + SignatureHelper sig = SignatureHelper.GetMethodSigHelper(CallingConventions.Standard, typeof(void)); + AssertExtensions.Throws(() => sig.AddArgument(Type.MakeFunctionPointerSignatureType(typeof(int), [typeof(bool)]))); + } } } diff --git a/src/libraries/System.Reflection.Emit.ILGeneration/tests/System.Reflection.Emit.ILGeneration.Tests.csproj b/src/libraries/System.Reflection.Emit.ILGeneration/tests/System.Reflection.Emit.ILGeneration.Tests.csproj index fbd22531f9188b..fe4cd116a9e751 100644 --- a/src/libraries/System.Reflection.Emit.ILGeneration/tests/System.Reflection.Emit.ILGeneration.Tests.csproj +++ b/src/libraries/System.Reflection.Emit.ILGeneration/tests/System.Reflection.Emit.ILGeneration.Tests.csproj @@ -3,6 +3,7 @@ $(NetCoreAppCurrent) true true + true diff --git a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx index b22c9a53610fee..0409122e6627e6 100644 --- a/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx +++ b/src/libraries/System.Reflection.Emit/src/Resources/Strings.resx @@ -303,4 +303,7 @@ Type provided must be an Enum. + + Argument must represent a function pointer type. + diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs index d8f3445eb2434f..abc4449dbe9be1 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs @@ -645,7 +645,7 @@ private static int GetStackChange(OpCode opcode, MethodInfo methodInfo, Type voi int stackChange = 0; // Push the return value if there is one. - if (methodInfo.ReturnType != voidType) + if (methodInfo.ReturnType.UnderlyingSystemType != voidType) { stackChange++; } @@ -720,11 +720,29 @@ public override void EmitCalli(OpCode opcode, CallingConvention unmanagedCallCon _il.Token(_moduleBuilder.GetSignatureToken(unmanagedCallConv, returnType, parameterTypes)); } + /// + public override void EmitCalli(Type functionPointerType) + { + ArgumentNullException.ThrowIfNull(functionPointerType); + + if (!functionPointerType.IsFunctionPointer) + throw new ArgumentException(SR.Argument_MustBeFunctionPointer, nameof(functionPointerType)); + + int stackChange = GetStackChange( + functionPointerType.GetFunctionPointerReturnType(), + _moduleBuilder.GetTypeFromCoreAssembly(CoreTypeId.Void), + functionPointerType.GetFunctionPointerParameterTypes()); + + UpdateStackSize(stackChange); + Emit(OpCodes.Calli); + _il.Token(_moduleBuilder.GetFunctionPointerSignatureToken(functionPointerType)); + } + private static int GetStackChange(Type? returnType, Type voidType, Type[]? parameterTypes) { int stackChange = 0; // If there is a non-void return type, push one. - if (returnType != voidType) + if (returnType?.UnderlyingSystemType != voidType) { stackChange++; } diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs index e87fa61926efb9..33b9cd9656192c 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs @@ -696,7 +696,8 @@ private EntityHandle GetTypeReferenceOrSpecificationHandle(Type type) if (!_typeReferences.TryGetValue(type, out var typeHandle)) { if (type.HasElementType || type.IsGenericParameter || - (type.IsGenericType && !type.IsGenericTypeDefinition)) + (type.IsGenericType && !type.IsGenericTypeDefinition) + || type.IsFunctionPointer) { typeHandle = AddTypeSpecification(type); } @@ -1103,6 +1104,19 @@ private bool IsConstructedFromTypeBuilder(Type type) return (elementType is TypeBuilderImpl tbi && Equals(tbi.Module)) || IsConstructedFromTypeBuilder(elementType); } + if (type.IsFunctionPointer) + { + Type ret = type.GetFunctionPointerReturnType(); + if ((ret is TypeBuilderImpl tb && Equals(tb.Module)) || IsConstructedFromTypeBuilder(ret)) + return true; + + foreach (Type paramType in type.GetFunctionPointerParameterTypes()) + { + if ((paramType is TypeBuilderImpl _tb && Equals(_tb.Module)) || IsConstructedFromTypeBuilder(paramType)) + return true; + } + } + return false; } @@ -1359,6 +1373,16 @@ internal int GetSignatureToken(CallingConvention callingConvention, Type? return MetadataTokens.GetToken(_metadataBuilder.AddStandaloneSignature(_metadataBuilder.GetOrAddBlob( MetadataSignatureHelper.GetMethodSignature(this, parameterTypes, returnType, GetSignatureConvention(callingConvention))))); + internal int GetFunctionPointerSignatureToken(Type functionPointerType) + { + BlobBuilder blobBuilder = new(); + SignatureTypeEncoder encoder = new(blobBuilder); + MetadataSignatureHelper.WriteSignatureForFunctionPointerType(encoder, functionPointerType, this); + + byte[] blob = blobBuilder.ToArray()[1..]; // Strip away ELEMENT_TYPE_FNPTR + return MetadataTokens.GetToken(_metadataBuilder.AddStandaloneSignature(_metadataBuilder.GetOrAddBlob(blob))); + } + private static SignatureCallingConvention GetSignatureConvention(CallingConvention callingConvention) => callingConvention switch { diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs index 2ec00b90743525..ebaa831e4b20f2 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs @@ -252,7 +252,7 @@ private static void WriteSignatureForType(SignatureTypeEncoder signature, Type t } } - private static void WriteSignatureForFunctionPointerType(SignatureTypeEncoder signature, Type type, ModuleBuilderImpl module) + internal static void WriteSignatureForFunctionPointerType(SignatureTypeEncoder signature, Type type, ModuleBuilderImpl module) { SignatureCallingConvention callConv = SignatureCallingConvention.Default; FunctionPointerAttributes attribs = FunctionPointerAttributes.None; @@ -295,7 +295,11 @@ private static void WriteSignatureForFunctionPointerType(SignatureTypeEncoder si if (returnType.GetRequiredCustomModifiers() is Type[] retModReqs) WriteCustomModifiers(retModifiersEncoder, retModReqs, isOptional: false, module); - WriteSignatureForType(retTypeEncoder.Type(), returnType, module); + Type returnTypeToWrite = returnType; + if (returnTypeToWrite.IsSignatureType) + returnTypeToWrite = returnTypeToWrite.UnderlyingSystemType; + + WriteSignatureForType(retTypeEncoder.Type(), returnTypeToWrite, module); foreach (Type paramType in paramTypes) { @@ -308,7 +312,11 @@ private static void WriteSignatureForFunctionPointerType(SignatureTypeEncoder si if (paramType.GetRequiredCustomModifiers() is Type[] paramModReqs) WriteCustomModifiers(paramModifiersEncoder, paramModReqs, isOptional: false, module); - WriteSignatureForType(paramEncoder.Type(), paramType, module); + Type paramTypeToWrite = paramType; + if (paramTypeToWrite.IsSignatureType) + paramTypeToWrite = paramTypeToWrite.UnderlyingSystemType; + + WriteSignatureForType(paramEncoder.Type(), paramTypeToWrite, module); } } diff --git a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveILGeneratorTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveILGeneratorTests.cs index 9c7f21ed239990..6470db395f4224 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveILGeneratorTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveILGeneratorTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Loader; using Xunit; @@ -3066,5 +3067,133 @@ public void ReferenceGenericMembersInOtherGeneratedAssembly() tlc.Unload(); } } + + [Fact] + public void EmitCalliFunctionPointerField() + { + using TempFile file = TempFile.Create(); + PersistedAssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyAndModule(out ModuleBuilder mb); + TypeBuilder tb = mb.DefineType("Program", TypeAttributes.Public); + MethodBuilder methb = tb.DefineMethod("Test", MethodAttributes.Public | MethodAttributes.Static); + methb.SetReturnType(typeof(int)); + ILGenerator il = methb.GetILGenerator(); + il.Emit(OpCodes.Call, typeof(ClassWithFunctionPointers).GetMethod("Init")); + il.Emit(OpCodes.Ldc_I4_2); + il.Emit(OpCodes.Ldc_I4_3); + il.Emit(OpCodes.Ldsfld, typeof(ClassWithFunctionPointers).GetField("FuncManaged")); + il.EmitCalli(Type.MakeFunctionPointerSignatureType(typeof(int), [typeof(int), typeof(int)])); + il.Emit(OpCodes.Ret); + tb.CreateType(); + ab.Save(file.Path); + + TestAssemblyLoadContext tlc = new(); + Assembly assemblyFromDisk = tlc.LoadFromAssemblyPath(file.Path); + Type typeFromDisk = assemblyFromDisk.GetType("Program"); + MethodInfo methodFromDisk = typeFromDisk.GetMethod("Test"); + int result = (int)methodFromDisk.Invoke(null, null); + + Assert.Equal(5, result); + tlc.Unload(); + } + + [Fact] + public void EmitCalliVerifySignatures() + { + Type t1 = Type.MakeFunctionPointerSignatureType(typeof(int), [typeof(int), typeof(int)]); + Type t2 = Type.MakeFunctionPointerSignatureType(typeof(bool), [typeof(string)], true, [typeof(CallConvCdecl)]); + Type t3 = Type.MakeFunctionPointerSignatureType(typeof(void), [typeof(int)], true, [typeof(CallConvFastcall), typeof(CallConvSuppressGCTransition)]); + Type t4 = Type.MakeFunctionPointerSignatureType( + typeof(string), + [Type.MakeFunctionPointerSignatureType(typeof(bool), [typeof(short)], true, [typeof(CallConvStdcall), typeof(CallConvMemberFunction)])], + true, [typeof(CallConvSwift)]); + + using TempFile file = TempFile.Create(); + PersistedAssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyAndModule(out ModuleBuilder mb); + TypeBuilder tb = mb.DefineType("Program", TypeAttributes.Public); + MethodBuilder methb = tb.DefineMethod("Test", MethodAttributes.Public | MethodAttributes.Static); + ILGenerator il = methb.GetILGenerator(); + Label labelAfter = il.DefineLabel(); + + il.Emit(OpCodes.Br, labelAfter); + il.EmitCalli(t1); + il.EmitCalli(t2); + il.EmitCalli(t3); + il.EmitCalli(t4); + il.MarkLabel(labelAfter); + il.Emit(OpCodes.Ret); + tb.CreateType(); + ab.Save(file.Path); + + // Verifies EmitCalli produces valid signatures -> InvalidProgramException is thrown otherwise + TestAssemblyLoadContext tlc = new(); + Assembly assemblyFromDisk = tlc.LoadFromAssemblyPath(file.Path); + Type typeFromDisk = assemblyFromDisk.GetType("Program"); + MethodInfo methodFromDisk = typeFromDisk.GetMethod("Test"); + methodFromDisk.Invoke(null, null); + tlc.Unload(); + } + + [Fact] + public void EmitCalliVerifyStackChange() + { + Type fieldType = Type.MakeFunctionPointerSignatureType( + Type.MakeModifiedSignatureType(typeof(void), [typeof(IsConst)], null), + null); + + using TempFile file = TempFile.Create(); + PersistedAssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyAndModule(out ModuleBuilder mb); + TypeBuilder tb = mb.DefineType("Program", TypeAttributes.Public); + FieldBuilder fb = tb.DefineField("Func", fieldType, FieldAttributes.Public | FieldAttributes.Static); + MethodBuilder methb = tb.DefineMethod("Test", MethodAttributes.Public | MethodAttributes.Static); + ILGenerator il = methb.GetILGenerator(); + + il.Emit(OpCodes.Ldftn, typeof(Console).GetMethod("WriteLine", BindingFlags.Public | BindingFlags.Static, [])); + il.Emit(OpCodes.Stsfld, fb); + il.Emit(OpCodes.Ldsfld, fb); + il.EmitCalli(fieldType); + il.Emit(OpCodes.Ret); + tb.CreateType(); + ab.Save(file.Path); + + TestAssemblyLoadContext tlc = new(); + Assembly assemblyFromDisk = tlc.LoadFromAssemblyPath(file.Path); + Type typeFromDisk = assemblyFromDisk.GetType("Program"); + MethodInfo methodFromDisk = typeFromDisk.GetMethod("Test"); + methodFromDisk.Invoke(null, null); + tlc.Unload(); + } + + [Fact] + public void EmitLdtokenSignatureType() + { + using TempFile file = TempFile.Create(); + PersistedAssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyAndModule(out ModuleBuilder mb); + TypeBuilder tb = mb.DefineType("MyType", TypeAttributes.Class | TypeAttributes.Public); + MethodBuilder methb = tb.DefineMethod("M", MethodAttributes.Public | MethodAttributes.Static); + methb.SetReturnType(typeof(Type)); + GenericTypeParameterBuilder genericTypeParam = methb.DefineGenericParameters(["T"])[0]; + ILGenerator il = methb.GetILGenerator(); + + Type sigType = Type.MakeFunctionPointerSignatureType( + typeof(DateTime), + [genericTypeParam, tb]); + + il.Emit(OpCodes.Ldtoken, sigType); + il.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle")); + il.Emit(OpCodes.Ret); + tb.CreateType(); + ab.Save(file.Path); + + TestAssemblyLoadContext tlc = new(); + Assembly assemblyFromDisk = tlc.LoadFromAssemblyPath(file.Path); + Type typeFromDisk = assemblyFromDisk.GetType("MyType"); + MethodInfo methodFromDisk = typeFromDisk.GetMethod("M").MakeGenericMethod(typeof(int)); + Type retType = (Type)methodFromDisk.Invoke(null, null); + tlc.Unload(); + + Assert.True(retType.IsFunctionPointer); + Assert.Equal(typeof(DateTime), retType.GetFunctionPointerReturnType()); + Assert.Equal([typeof(int).FullName, tb.FullName], retType.GetFunctionPointerParameterTypes().Select(t => t.FullName)); + } } } diff --git a/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj b/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj index 0552a0ae8f6473..d44c7b9995b8b3 100644 --- a/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj +++ b/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj @@ -122,6 +122,7 @@ + diff --git a/src/libraries/System.Reflection.Emit/tests/TypeBuilder/TypeBuilderMakeFunctionPointerType.cs b/src/libraries/System.Reflection.Emit/tests/TypeBuilder/TypeBuilderMakeFunctionPointerType.cs new file mode 100644 index 00000000000000..dc7e32fa5f177f --- /dev/null +++ b/src/libraries/System.Reflection.Emit/tests/TypeBuilder/TypeBuilderMakeFunctionPointerType.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.Reflection.Emit.Tests +{ + public class TypeBuilderMakeFunctionPointerType + { + [Fact] + public void MakeFunctionPointerType() + { + TypeBuilder retType = Helpers.DynamicType(TypeAttributes.Public); + Type[] paramTypes = [typeof(int), typeof(bool)]; + Type fnPtrType = retType.MakeFunctionPointerType(paramTypes); + + Assert.True(fnPtrType.IsFunctionPointer); + Assert.False(fnPtrType.IsUnmanagedFunctionPointer); + Assert.Equal(retType, fnPtrType.GetFunctionPointerReturnType()); + Assert.Equal(paramTypes, fnPtrType.GetFunctionPointerParameterTypes()); + } + } +} diff --git a/src/libraries/System.Reflection.Emit/tests/Utilities.cs b/src/libraries/System.Reflection.Emit/tests/Utilities.cs index 446b33f8628aa9..efd754177e0c08 100644 --- a/src/libraries/System.Reflection.Emit/tests/Utilities.cs +++ b/src/libraries/System.Reflection.Emit/tests/Utilities.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Xunit; namespace System.Reflection.Emit.Tests @@ -214,4 +216,15 @@ public ModifiedType(Type delegatingType, Type[] requiredMods = null, Type[] opti public override Type[] GetOptionalCustomModifiers() => optionalModifiers; } } + + public unsafe class ClassWithFunctionPointers + { + public static delegate* FuncManaged; + public static int Add(int a, int b) => a + b; + public static void Init() => FuncManaged = &Add; + + public static delegate* unmanaged[Cdecl] FuncUnmanaged1; + public static delegate* unmanaged[Fastcall, SuppressGCTransition] FuncUnmanaged2; + public static delegate* unmanaged[Swift], string> FuncUnmanaged3; + } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index ffa9bf58bdcecf..ca4358fdf536d9 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -6771,11 +6771,14 @@ protected Type() { } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("The code for an array of the specified type might not be available.")] public virtual System.Type MakeArrayType(int rank) { throw null; } public virtual System.Type MakeByRefType() { throw null; } + public static System.Type MakeFunctionPointerSignatureType(System.Type returnType, System.Type[]? parameterTypes, bool isUnmanaged = false, System.Type[]? callingConventions = null) { throw null; } + public virtual System.Type MakeFunctionPointerType(System.Type[]? parameterTypes, bool isUnmanaged = false) { throw null; } public static System.Type MakeGenericMethodParameter(int position) { throw null; } public static System.Type MakeGenericSignatureType(System.Type genericTypeDefinition, params System.Type[] typeArguments) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("The native code for this instantiation might not be available at runtime.")] [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("If some of the generic arguments are annotated (either with DynamicallyAccessedMembersAttribute, or generic constraints), trimming can't validate that the requirements of those annotations are met.")] public virtual System.Type MakeGenericType(params System.Type[] typeArguments) { throw null; } + public static System.Type MakeModifiedSignatureType(System.Type type, System.Type[]? requiredCustomModifiers, System.Type[]? optionalCustomModifiers) { throw null; } public virtual System.Type MakePointerType() { throw null; } public static bool operator ==(System.Type? left, System.Type? right) { throw null; } public static bool operator !=(System.Type? left, System.Type? right) { throw null; } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/SignatureTypes.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/SignatureTypes.cs index 2c346f23e05796..16edcceb23f7d4 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/SignatureTypes.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/SignatureTypes.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Runtime.CompilerServices; using Xunit; namespace System.Reflection.Tests @@ -298,6 +299,215 @@ public static void MakeSignaturePointerType() TestSignatureTypeInvariants(t); } + [Fact] + public static void MakeSignatureFunctionPointerTypeManaged() + { + Type[] paramTypes = [typeof(string), typeof(bool)]; + Type t = Type.MakeFunctionPointerSignatureType(typeof(int), paramTypes); + + Assert.True(t.IsFunctionPointer); + Assert.False(t.IsUnmanagedFunctionPointer); + Assert.Equal(typeof(int).ToString(), t.GetFunctionPointerReturnType().ToString()); + Assert.Equal(paramTypes.Select(t => t.ToString()), t.GetFunctionPointerParameterTypes().Select(t => t.ToString())); + Assert.Equal(0, t.GetFunctionPointerCallingConventions().Length); + } + + [Fact] + public static void MakeSignatureFunctionPointerTypeManaged_InvalidCallingConventions() + { + Assert.Throws(() => + { + Type[] paramTypes = [typeof(string), typeof(bool)]; + Type t = Type.MakeFunctionPointerSignatureType(typeof(int), paramTypes, false, [typeof(CallConvCdecl)]); + }); + } + + [Fact] + public static void MakeSignatureFunctionPointerTypeUnmanaged1() + { + Type[] paramTypes = [typeof(short)]; + Type[] callConvs = [typeof(CallConvCdecl)]; + Type t = Type.MakeFunctionPointerSignatureType(typeof(void), paramTypes, true, callConvs); + + Type fnPtrRet = t.GetFunctionPointerReturnType(); + Type[] fnPtrParams = t.GetFunctionPointerParameterTypes(); + Type[] fnPtrCallConvs = t.GetFunctionPointerCallingConventions(); + Assert.True(t.IsFunctionPointer); + Assert.True(t.IsUnmanagedFunctionPointer); + Assert.Equal(typeof(void).ToString(), fnPtrRet.ToString()); + Assert.Equal(paramTypes.Select(t => t.ToString()), fnPtrParams.Select(t => t.ToString())); + Assert.Equal(callConvs.Select(t => t.ToString()), fnPtrCallConvs.Select(t => t.ToString())); + Assert.Equal(0, fnPtrRet.GetOptionalCustomModifiers().Length); + } + + [Fact] + public static void MakeSignatureFunctionPointerTypeUnmanaged2() + { + Type[] paramTypes = [typeof(short)]; + Type[] callConvs = [typeof(CallConvSwift)]; + Type t = Type.MakeFunctionPointerSignatureType(typeof(void), paramTypes, true, callConvs); + + Type fnPtrRet = t.GetFunctionPointerReturnType(); + Type[] fnPtrParams = t.GetFunctionPointerParameterTypes(); + Type[] fnPtrCallConvs = t.GetFunctionPointerCallingConventions(); + Assert.True(t.IsFunctionPointer); + Assert.True(t.IsUnmanagedFunctionPointer); + Assert.Equal(typeof(void).ToString(), fnPtrRet.ToString()); + Assert.Equal(paramTypes.Select(t => t.ToString()), fnPtrParams.Select(t => t.ToString())); + Assert.Equal(callConvs.Select(t => t.ToString()), fnPtrCallConvs.Select(t => t.ToString())); + Assert.Equal(1, fnPtrRet.GetOptionalCustomModifiers().Length); + } + + [Fact] + public static void MakeSignatureFunctionPointerTypeUnmanaged3() + { + Type[] paramTypes = [typeof(short)]; + Type[] callConvs = [typeof(CallConvCdecl), typeof(CallConvSuppressGCTransition)]; + Type t = Type.MakeFunctionPointerSignatureType(typeof(void), paramTypes, true, callConvs); + callConvs.Reverse(); + + Type fnPtrRet = t.GetFunctionPointerReturnType(); + Type[] fnPtrParams = t.GetFunctionPointerParameterTypes(); + Type[] fnPtrCallConvs = t.GetFunctionPointerCallingConventions(); + Assert.True(t.IsFunctionPointer); + Assert.True(t.IsUnmanagedFunctionPointer); + Assert.Equal(typeof(void).ToString(), fnPtrRet.ToString()); + Assert.Equal(paramTypes.Select(t => t.ToString()), fnPtrParams.Select(t => t.ToString())); + Assert.Equal(callConvs.Select(t => t.ToString()), fnPtrCallConvs.Select(t => t.ToString())); + Assert.Equal(2, fnPtrRet.GetOptionalCustomModifiers().Length); + } + + private static void AssertFunctionPointerTypesEqual(Type expected, Type actual) + { + Assert.Equal(expected.ToString(), actual.ToString()); + Assert.Equal(expected.IsFunctionPointer, actual.IsFunctionPointer); + Assert.Equal(expected.IsUnmanagedFunctionPointer, actual.IsUnmanagedFunctionPointer); + Assert.Equal(expected.GetFunctionPointerReturnType().UnderlyingSystemType, actual.GetFunctionPointerReturnType().UnderlyingSystemType); + Assert.Equal(expected.GetFunctionPointerReturnType().GetOptionalCustomModifiers(), actual.GetFunctionPointerReturnType().GetOptionalCustomModifiers()); + Assert.Equal(expected.GetFunctionPointerParameterTypes().Select(p => p.UnderlyingSystemType), actual.GetFunctionPointerParameterTypes().Select(p => p.UnderlyingSystemType)); + Assert.Equal(expected.GetFunctionPointerCallingConventions(), actual.GetFunctionPointerCallingConventions()); + } + + public static IEnumerable SignatureFunctionPointerTypesTestData + { + get + { + yield return + [ + Type.MakeFunctionPointerSignatureType(typeof(int), [typeof(int), typeof(int)]), + typeof(ClassWithFunctionPointers).GetField("Func1").GetModifiedFieldType() + ]; + + yield return + [ + Type.MakeFunctionPointerSignatureType(typeof(bool), [typeof(string)], true, [typeof(CallConvCdecl)]), + typeof(ClassWithFunctionPointers).GetField("Func2").GetModifiedFieldType() + ]; + + yield return + [ + Type.MakeFunctionPointerSignatureType(typeof(void), [typeof(int)], true, [typeof(CallConvFastcall), typeof(CallConvSuppressGCTransition)]), + typeof(ClassWithFunctionPointers).GetField("Func3").GetModifiedFieldType() + ]; + } + } + + [Theory] + [MemberData(nameof(SignatureFunctionPointerTypesTestData))] + public static void MakeSignatureFunctionPointerType_MatchesGetModifiedFieldType(Type signatureType, Type reflectedType) + { + AssertFunctionPointerTypesEqual(reflectedType, signatureType); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/90308", TestRuntimes.Mono)] + public static void MakeSignatureFunctionPointerType_MatchesGetModifiedFieldType_NestedFunctionPointer() + { + Type expected = typeof(ClassWithFunctionPointers).GetField("Func4").GetModifiedFieldType(); + Type actual = Type.MakeFunctionPointerSignatureType( + typeof(string), + [Type.MakeFunctionPointerSignatureType(typeof(bool), [typeof(short)], true, [typeof(CallConvStdcall), typeof(CallConvMemberFunction)])], + true, [typeof(CallConvSwift)]); + + Assert.Equal(expected.ToString(), actual.ToString()); + Assert.Equal(expected.IsFunctionPointer, actual.IsFunctionPointer); + Assert.Equal(expected.IsUnmanagedFunctionPointer, actual.IsUnmanagedFunctionPointer); + Assert.Equal(expected.GetFunctionPointerReturnType().UnderlyingSystemType, actual.GetFunctionPointerReturnType().UnderlyingSystemType); + Assert.Equal(expected.GetFunctionPointerCallingConventions(), actual.GetFunctionPointerCallingConventions()); + AssertFunctionPointerTypesEqual(expected.GetFunctionPointerParameterTypes()[0], actual.GetFunctionPointerParameterTypes()[0]); + } + + [Fact] + public static void MakeFunctionPointerType_FromSignatureType() + { + Type retType = Type.MakeFunctionPointerSignatureType(typeof(int), []); + Type fnPtrType = retType.MakeFunctionPointerType([typeof(bool)]); + + Assert.True(fnPtrType.IsSignatureType); + Assert.True(fnPtrType.IsFunctionPointer); + AssertFunctionPointerTypesEqual(retType, fnPtrType.GetFunctionPointerReturnType()); + } + + [Fact] + public static void MakeSignatureModifiedType() + { + Type t = Type.MakeModifiedSignatureType(typeof(List), [], [typeof(IsVolatile)]); + + Assert.True(t.IsGenericType); + Assert.Equal(typeof(int).ToString(), t.GetGenericArguments()[0].ToString()); + Assert.Equal(0, t.GetRequiredCustomModifiers().Length); + Assert.Equal(1, t.GetOptionalCustomModifiers().Length); + Assert.Equal(typeof(IsVolatile).ToString(), t.GetOptionalCustomModifiers()[0].ToString()); + } + + [Fact] + public static void MakeSignatureModifiedType_Nested1() + { + Type type = Type.MakeModifiedSignatureType( + Type.MakeModifiedSignatureType( + typeof(int), + [typeof(IsVolatile)], []), + [typeof(IsConst)], []); + + Assert.True(type.IsSignatureType); + Assert.True(type.UnderlyingSystemType == typeof(int)); + Assert.Equal([typeof(IsVolatile), typeof(IsConst)], type.GetRequiredCustomModifiers()); + Assert.Equal([], type.GetOptionalCustomModifiers()); + } + + [Fact] + public static void MakeSignatureModifiedType_Nested2() + { + Type type = Type.MakeModifiedSignatureType( + Type.MakeModifiedSignatureType( + typeof(bool), + [], + [typeof(CallConvSuppressGCTransition), typeof(CallConvCdecl)]), + [typeof(IsConst)], []); + + Assert.True(type.IsSignatureType); + Assert.True(type.UnderlyingSystemType == typeof(bool)); + Assert.Equal([typeof(IsConst)], type.GetRequiredCustomModifiers()); + Assert.Equal([typeof(CallConvSuppressGCTransition), typeof(CallConvCdecl)], type.GetOptionalCustomModifiers()); + } + + [Fact] + public static void MakeSignatureModifiedType_Nested3() + { + Type type = Type.MakeModifiedSignatureType( + Type.MakeModifiedSignatureType( + Type.MakeModifiedSignatureType( + typeof(long), + [typeof(IsLong)], []), + [typeof(IsConst)], []), + [typeof(IsVolatile)], []); + + Assert.True(type.IsSignatureType); + Assert.True(type.UnderlyingSystemType == typeof(long)); + Assert.Equal([typeof(IsLong), typeof(IsConst), typeof(IsVolatile)], type.GetRequiredCustomModifiers()); + Assert.Equal([], type.GetOptionalCustomModifiers()); + } + [Theory] [InlineData(typeof(List<>))] [InlineData(typeof(Span<>))] @@ -434,6 +644,14 @@ public enum GenericEnum } } + private unsafe class ClassWithFunctionPointers + { + public static delegate* Func1 = null; + public static delegate* unmanaged[Cdecl] Func2 = null; + public static delegate* unmanaged[Fastcall, SuppressGCTransition] Func3 = null; + public static delegate* unmanaged[Swift], string> Func4 = null; + } + private static void TestSignatureTypeInvariants(Type type) { Assert.True(type.IsSignatureType); diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Type/TypeTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Type/TypeTests.cs index 2424af0f4fbe91..a55d9d57180823 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Type/TypeTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Type/TypeTests.cs @@ -492,6 +492,99 @@ public void MakePointerType_ByRef_ThrowsTypeLoadException() Assert.Throws(() => t.MakePointerType()); } + public static IEnumerable MakeFunctionPointerType_TestData() + { + yield return new object[] { typeof(void), Type.EmptyTypes }; + yield return new object[] { typeof(int), new Type[] { typeof(string) } }; + yield return new object[] { typeof(string), new Type[] { typeof(int), typeof(double) } }; + yield return new object[] { typeof(int*), new Type[] { typeof(int*) } }; + yield return new object[] { typeof(int[]), new Type[] { typeof(string[]) } }; + yield return new object[] { typeof(GenericClass), new Type[] { typeof(GenericStruct) } }; + yield return new object[] { typeof(int), new Type[] { typeof(int).MakeByRefType() } }; + } + + [Theory] + [MemberData(nameof(MakeFunctionPointerType_TestData))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/124149", TestRuntimes.Mono)] + public void MakeFunctionPointerType_Invoke_ReturnsExpected(Type returnType, Type[] parameterTypes) + { + Type fnPtrType = returnType.MakeFunctionPointerType(parameterTypes); + + Assert.True(fnPtrType.IsFunctionPointer); + Assert.False(fnPtrType.IsUnmanagedFunctionPointer); + Assert.Equal(returnType, fnPtrType.GetFunctionPointerReturnType()); + Assert.Equal(parameterTypes, fnPtrType.GetFunctionPointerParameterTypes()); + + Assert.Equal(fnPtrType, returnType.MakeFunctionPointerType(parameterTypes)); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/124149", TestRuntimes.Mono)] + public void MakeFunctionPointerType_NullParameters_ReturnsExpected() + { + Type fnPtrType = typeof(int).MakeFunctionPointerType(null); + + Assert.True(fnPtrType.IsFunctionPointer); + Assert.Empty(fnPtrType.GetFunctionPointerParameterTypes()); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/124149", TestRuntimes.Mono)] + public void MakeFunctionPointerType_Unmanaged_ReturnsExpected() + { + Type[] parameterTypes = [typeof(int), typeof(double)]; + Type fnPtrManaged = typeof(int).MakeFunctionPointerType(parameterTypes, isUnmanaged: false); + Type fnPtrUnmanaged = typeof(int).MakeFunctionPointerType(parameterTypes, isUnmanaged: true); + + Assert.False(fnPtrManaged.IsUnmanagedFunctionPointer); + Assert.True(fnPtrUnmanaged.IsUnmanagedFunctionPointer); + Assert.NotEqual(fnPtrManaged, fnPtrUnmanaged); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/124149", TestRuntimes.Mono)] + public void MakeFunctionPointerType_ParameterArrayIsCloned() + { + Type[] parameterTypes = [typeof(int), typeof(string)]; + Type fnPtrType = typeof(int).MakeFunctionPointerType(parameterTypes); + + parameterTypes[0] = typeof(double); + + Assert.Equal(typeof(int), fnPtrType.GetFunctionPointerParameterTypes()[0]); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/124149", TestRuntimes.Mono)] + public void MakeFunctionPointerType_NullParameterInArray_ThrowsArgumentNullException() + { + Type[] parameterTypes = [typeof(int), null!, typeof(string)]; + Assert.Throws("parameterTypes", () => typeof(int).MakeFunctionPointerType(parameterTypes)); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/124149", TestRuntimes.Mono)] + public void MakeFunctionPointerType_InvalidArgumentTypes() + { + Type[] voidParam = [typeof(void)]; + Type[] openGenericParam = [typeof(List<>)]; + + Assert.Throws("parameterTypes", () => typeof(void).MakeFunctionPointerType(voidParam)); + Assert.Throws("parameterTypes", () => typeof(void).MakeFunctionPointerType(openGenericParam)); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/124149", TestRuntimes.Mono)] + public void MakeFunctionPointerType_WithSignatureTypeParameter() + { + Type paramType = Type.MakeGenericMethodParameter(0); + Type fnPtrType = typeof(int).MakeFunctionPointerType([paramType]); + + Assert.True(fnPtrType.IsSignatureType); + Assert.True(fnPtrType.IsFunctionPointer); + Assert.Equal(typeof(int), fnPtrType.GetFunctionPointerReturnType()); + Assert.Equal(paramType, fnPtrType.GetFunctionPointerParameterTypes()[0]); + } + [Theory] [InlineData("System.Nullable`1[System.Int32]", typeof(int?))] [InlineData("System.Int32*", typeof(int*))]