Skip to content

New function pointer APIs#123819

Open
jgh07 wants to merge 19 commits intodotnet:mainfrom
jgh07:issue-75348
Open

New function pointer APIs#123819
jgh07 wants to merge 19 commits intodotnet:mainfrom
jgh07:issue-75348

Conversation

@jgh07
Copy link
Contributor

@jgh07 jgh07 commented Jan 30, 2026

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.

Copilot AI review requested due to automatic review settings January 30, 2026 22:34
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Jan 30, 2026
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-reflection
See info in area-owners.md if you want to be subscribed.

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,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.MakeFunctionPointerSignatureType and Type.MakeModifiedSignatureType static 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;

Copilot AI review requested due to automatic review settings February 2, 2026 13:32
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 7 comments.

Copilot AI review requested due to automatic review settings February 2, 2026 17:42
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 3 comments.

Copilot AI review requested due to automatic review settings February 2, 2026 19:05
@jgh07
Copy link
Contributor Author

jgh07 commented Feb 2, 2026

Since all methods from the API proposal are implemented now, I am removing the "Draft" marking.

About the suggestion of changing MakeFunctionPointerSignatureType to take a non-nullable Type for the return type, is that an acceptable deviation from the approved API shape or does it need to go through the review board again?

@jgh07 jgh07 marked this pull request as ready for review February 2, 2026 19:09
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated 1 comment.

@jkotas
Copy link
Member

jkotas commented Feb 2, 2026

About the suggestion of changing MakeFunctionPointerSignatureType to take a non-nullable Type for the return type, is that an acceptable deviation from the approved API shape

I think it is acceptable deviation.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 2, 2026 19:32
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

returnType,
parameterTypes: [typeof(string)],
isUnmanaged: true,
callingConventions: [typeof(CallConvCdecl)]));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Contributor Author

@jgh07 jgh07 Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot AI review requested due to automatic review settings February 6, 2026 19:47
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated 7 comments.

Comment on lines +19 to +23
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;
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +3079 to +3083
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)]));
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 6, 2026 20:44
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated 1 comment.


// Push the return value if there is one.
if (methodInfo.ReturnType != voidType)
if (methodInfo.ReturnType.UnderlyingSystemType != voidType)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this check for function pointers too?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Reflection community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants