Conversation
…, ILGeneratorImpl.EmitCalli(Type)
|
Tagging subscribers to this area: @dotnet/area-system-reflection |
| public abstract void EmitCalli(OpCode opcode, CallingConvention unmanagedCallConv, Type? returnType, Type[]? parameterTypes); | ||
|
|
||
| /// <summary> | ||
| /// Puts a <see cref="OpCodes.Calli"/> instruction onto the Microsoft intermediate language (MSIL) stream, |
There was a problem hiding this comment.
I kept this description largely the same as the existing overloads. I think it might make more sense to update it though, since as far as I'm concerned the term MSIL was dropped in favor of CIL.
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
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
|
Since all methods from the API proposal are implemented now, I am removing the "Draft" marking. About the suggestion of changing |
src/libraries/System.Private.CoreLib/src/System/Reflection/SignatureFunctionPointerType.cs
Outdated
Show resolved
Hide resolved
I think it is acceptable deviation. |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
| returnType, | ||
| parameterTypes: [typeof(string)], | ||
| isUnmanaged: true, | ||
| callingConventions: [typeof(CallConvCdecl)])); |
There was a problem hiding this comment.
CallConvCdecl and CallConvStdcall are encoded in same way. It would be more interesting to test one of the calling conventions with more complicated encoding, ,like CallConvCdecl + CallConvMemberFunction
There was a problem hiding this comment.
Updating the test immediately caused it to fail – good catch.
I pushed a commit that provides the underlying infrastructure to deal with modopt-encoded calling conventions. For now the test still fails (for a different reason), I will investigate further tomorrow.
There was a problem hiding this comment.
This should work now as of 5208c79.
I had to dig pretty deep to fix it, further complicated by the fact that unlike ELEMENT_TYPE_INTENRAL, ELEMENT_TYPE_CMOD_INTERNAL is not documented anywhere and is not even part of the CorElementType enum. I assume it is just a private CoreCLR implementation detail.
| 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; |
There was a problem hiding this comment.
SignatureModifiedType overrides UnderlyingSystemType to the unmodified type, but it doesn't override Equals/GetHashCode. Since Type.Equals(Type) and Type.GetHashCode() are based on UnderlyingSystemType, this makes a SignatureModifiedType compare equal (and hash equal) to the unmodified type, which is very surprising for signature types. Consider overriding Equals/GetHashCode to preserve reference-based identity (like other SignatureType implementations), even if UnderlyingSystemType must point at the unmodified type for encoding.
src/libraries/System.Private.CoreLib/src/System/Reflection/SignatureFunctionPointerType.cs
Outdated
Show resolved
Hide resolved
| il.Emit(OpCodes.Call, typeof(ClassWithFunctionPointer).GetMethod("Init")); | ||
| il.Emit(OpCodes.Ldc_I4_2); | ||
| il.Emit(OpCodes.Ldc_I4_3); | ||
| il.Emit(OpCodes.Ldsfld, typeof(ClassWithFunctionPointer).GetField("Method")); | ||
| il.EmitCalli(Type.MakeFunctionPointerSignatureType(typeof(int), [typeof(int), typeof(int)])); |
There was a problem hiding this comment.
These reflection lookups can return null (e.g., if names change), which would make the test fail with a NullReferenceException instead of a clear assertion failure. Consider using null-forgiving operators or Assert/ThrowHelper checks to ensure GetMethod/GetField results are non-null before emitting IL.
src/libraries/System.Reflection.Emit.ILGeneration/ref/System.Reflection.Emit.ILGeneration.cs
Show resolved
Hide resolved
src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/ILGeneratorImpl.cs
Show resolved
Hide resolved
|
|
||
| // Push the return value if there is one. | ||
| if (methodInfo.ReturnType != voidType) | ||
| if (methodInfo.ReturnType.UnderlyingSystemType != voidType) |
There was a problem hiding this comment.
Should we have a test for the case that this is fixing?
| 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) { throw null; } |
There was a problem hiding this comment.
| public virtual void EmitCalli(System.Type functionPointerType) { throw null; } | |
| public virtual void EmitCalli(System.Type functionPointerType) { } |
Nit - consistent formatting with the next line and with what the autogenerator for ref files generates.
| il.Emit(OpCodes.Ret); | ||
|
|
||
| StringReverseCdecl del = new(StringReverse); | ||
| IntPtr funcPtr = Marshal.GetFunctionPointerForDelegate(del); |
There was a problem hiding this comment.
The calling convention of the function that this is going to call needs to match the calling convention of the calli. Delegate marshalling does not support more complicated signatures like that. This needs to use a method annotated with UnmanagedCallersOnly and the matching calling convention as the target.
Also, this should exercise a signature where typeof(CallConvCdecl), typeof(CallConvMemberFunction)]) makes a difference from the default calling conventions on some platforms at least. For example, something like this:
struct TwoLongs
{
long a, b;
}
[UnmanagedCallersOnly(CallConvs = new [] {typeof(CallConvCdecl), typeof(CallConvMemberFunction)})]
TwoLongs MethodWithReturnBuffer(IntPtr pThis) => *(TwoLongs*)pThis;
| throw new ArgumentException(SR.Argument_ArraysInvalid, nameof(requiredCustomModifiers)); | ||
|
|
||
| if (t.ContainsGenericParameters) | ||
| throw new ArgumentException(SR.Argument_GenericsInvalid, nameof(requiredCustomModifiers)); |
There was a problem hiding this comment.
Should this check for function pointers too?
This implements #75348.
The APIs from the proposal are all implemented now, but there are still some rough edges and design considerations needing to be discussed.