Conversation
…, ILGeneratorImpl.EmitCalli(Type)
|
Tagging subscribers to this area: @dotnet/area-system-reflection |
src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ILGenerator.cs
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/Reflection/SignatureType.cs
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
This PR implements new function pointer APIs as proposed in issue #75348, enabling better support for function pointer types in IL generation and reflection scenarios.
Changes:
- Adds
Type.MakeFunctionPointerSignatureTypeandType.MakeModifiedSignatureTypestatic factory methods for creating signature types - Implements
ILGenerator.EmitCalli(Type functionPointerType)overload in ILGeneratorImpl for modern function pointer calling conventions - Adds new internal SignatureFunctionPointerType and SignatureModifiedType classes to represent these signature types
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| System.Runtime.cs | Adds public API surface for new function pointer and modified signature type factory methods |
| Type.cs | Implements MakeFunctionPointerSignatureType and MakeModifiedSignatureType factory methods with XML documentation |
| SignatureFunctionPointerType.cs | New internal class representing function pointer signature types with support for calling conventions |
| SignatureModifiedType.cs | New internal class representing types with required/optional custom modifiers |
| SignatureType.cs | Changes UnderlyingSystemType from sealed to virtual to allow SignatureModifiedType to override it |
| ILGenerator.cs | Adds new EmitCalli overload accepting function pointer Type with XML documentation |
| ILGeneratorImpl.cs | Implements the new EmitCalli overload with stack tracking and signature token generation |
| SignatureHelper.cs | Makes WriteSignatureForFunctionPointerType internal and adds logic to unwrap SignatureTypes |
| ModuleBuilderImpl.cs | Adds GetFunctionPointerSignatureToken method to generate standalone signatures for calli instructions |
| DynamicILGenerator.cs | Adds stub TODO implementation for EmitCalli - not yet functional |
| System.Reflection.Emit.ILGeneration.cs | Adds public API surface for new EmitCalli overload |
| Strings.resx | Adds error message for invalid function pointer type arguments |
| System.Private.CoreLib.Shared.projitems | Adds new SignatureFunctionPointerType and SignatureModifiedType to project, plus unrelated whitespace fix |
| SignatureTypes.cs | Adds tests for the new MakeFunctionPointerSignatureType and MakeModifiedSignatureType methods |
| AssemblySaveILGeneratorTests.cs | Adds end-to-end test for EmitCalli with function pointer types |
| Utilities.cs | Adds ClassWithFunctionPointer test helper class |
Comments suppressed due to low confidence (1)
src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs:6
- This using directive appears to be unused. There are no references to System.IO types in this file. Consider removing it to keep the code clean.
using System.Reflection.Metadata;
src/libraries/System.Private.CoreLib/src/System/Reflection/SignatureFunctionPointerType.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/Reflection/SignatureModifiedType.cs
Outdated
Show resolved
Hide resolved
src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicILGenerator.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
Outdated
Show resolved
Hide resolved
src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/Reflection/SignatureFunctionPointerType.cs
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/Reflection/SignatureModifiedType.cs
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/Reflection/SignatureType.cs
Show resolved
Hide resolved
…ointerSignatureType
src/libraries/System.Reflection.Emit.ILGeneration/tests/ILGenerator/Emit4Tests.cs
Show resolved
Hide resolved
src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/SignatureTypes.cs
Show resolved
Hide resolved
|
I think the calling conventions specified via the explicit arg should always win. I do not think we should be smart about merging. We can throw an exception for now and see whether it causes issues in practices. We can replace the exception with merging later if it proves to be necessary. So something like: If there are modopt calling conventions on the return type, they have to match the (non-built in subset of) calling conventions passed via the explicit arg. If they do not match, throw an exception. |
| 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))); | ||
| } |
There was a problem hiding this comment.
GetFunctionPointerSignatureToken currently materializes the full blob to an array and then slices it ([1..]) to remove the leading ELEMENT_TYPE_FNPTR byte, which results in extra allocations (ToArray plus the slice copy). Consider emitting the standalone method signature blob without the ELEMENT_TYPE_FNPTR prefix in the first place (e.g., using a MethodSignatureEncoder / BlobEncoder directly, or writing into a second BlobBuilder starting at offset 1) to avoid the extra allocations when emitting many calli signatures.
src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/ILGenerator.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ModuleBuilderImpl.cs
Outdated
Show resolved
Hide resolved
| foreach (Type callConv in callingConventions) | ||
| ArgumentNullException.ThrowIfNull(callConv, nameof(callingConventions)); | ||
|
|
There was a problem hiding this comment.
MakeFunctionPointerSignatureType currently only null-checks the callingConventions array elements, but doesn’t validate that they are actually calling-convention marker types (e.g., System.Runtime.CompilerServices.CallConv*). As-is, callers can pass arbitrary types (or signature types) and get a Type whose GetFunctionPointerCallingConventions() returns values that cannot be meaningfully encoded/round-tripped as calling conventions. Consider validating each entry (e.g., FullName starts with CallingConventionTypePrefix, not HasElementType, not ContainsGenericParameters, and not IsSignatureType) and throwing ArgumentException for invalid entries.
| foreach (Type callConv in callingConventions) | |
| ArgumentNullException.ThrowIfNull(callConv, nameof(callingConventions)); | |
| foreach (Type callConv in callingConventions) | |
| { | |
| ArgumentNullException.ThrowIfNull(callConv, nameof(callingConventions)); | |
| if (callConv.HasElementType || | |
| callConv.ContainsGenericParameters || | |
| callConv.IsSignatureType || | |
| callConv.FullName is not string fullName || | |
| !fullName.StartsWith(CallingConventionTypePrefix, StringComparison.Ordinal)) | |
| { | |
| throw new ArgumentException( | |
| "Each calling convention type must be a non-generic, non-signature, non-element type whose full name starts with System.Runtime.CompilerServices.CallConv.", | |
| nameof(callingConventions)); | |
| } | |
| } |
There was a problem hiding this comment.
@jkotas Should we add this check after all? Since the modopt validation logic also assumes that the callingConventions elements adhere to this.
There was a problem hiding this comment.
I guess we can add it. It is easier to relax the validation after the fact if it proves to be problematic.
...raries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs
Show resolved
Hide resolved
...raries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs
Show resolved
Hide resolved
| else if (callConvInModOpts) | ||
| { | ||
| if (callConvIndex != callingConventions.Length) | ||
| throw new ArgumentException(SR.FunctionPointer_CallingConventionsUnbalanced, nameof(returnType)); | ||
|
|
||
| callConvsFinished = true; | ||
| } |
There was a problem hiding this comment.
| else if (callConvInModOpts) | |
| { | |
| if (callConvIndex != callingConventions.Length) | |
| throw new ArgumentException(SR.FunctionPointer_CallingConventionsUnbalanced, nameof(returnType)); | |
| callConvsFinished = true; | |
| } |
I think it is ok to allow calling conventions to be interleaved with other modopts.
There was a problem hiding this comment.
I think we can use callConvIndex != 0 instead of callConvInModOpts, and delete callConvInModOpts if you implement this suggestion.
| if (callConvsFinished) | ||
| throw new ArgumentException(SR.FunctionPointer_CallingConventionsUnbalanced, nameof(returnType)); | ||
|
|
||
| callConvInModOpts = true; | ||
|
|
||
| if (builtInCallConv && IsBuiltInCallingConvention(typeName)) | ||
| throw new ArgumentException(SR.FunctionPointer_CallingConventionsUnbalanced, nameof(returnType)); |
There was a problem hiding this comment.
| if (callConvsFinished) | |
| throw new ArgumentException(SR.FunctionPointer_CallingConventionsUnbalanced, nameof(returnType)); | |
| callConvInModOpts = true; | |
| if (builtInCallConv && IsBuiltInCallingConvention(typeName)) | |
| throw new ArgumentException(SR.FunctionPointer_CallingConventionsUnbalanced, nameof(returnType)); | |
| if (builtInCallConv) | |
| throw new ArgumentException(SR.FunctionPointer_CallingConventionsUnbalanced, nameof(returnType)); |
I do not think we need to re-check the typeName here. If we have a built-in callconv, we cannot have any callconvs in modopts.
This implements #75348.
MakeFunctionPointerTypeis only implemented for CoreCLR, a Mono implementation will follow.