Skip to content

Conversation

@jgh07
Copy link
Contributor

@jgh07 jgh07 commented Oct 27, 2025

This fixes #120909.

There is one small issue with the tests when the calling conventions are encoded as modopts: The PersistedAssemblyBuilder from the test specifies System.Private.CoreLib as its core assembly, which prevents the runtime from matching the signatures with the ones defined in C# (where the modifier types are from System.Runtime). This is seemingly not an issue with parameter types, but only with modifiers. @jkotas or anyone else, do you have an idea how to fix this? If the AssemblyBuilder used the same core assembly as the C# program, it should work. For now, I have commented out this specific field reference.

Copilot AI review requested due to automatic review settings October 27, 2025 23:11
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Oct 27, 2025
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 adds support for emitting references to unmanaged function pointer types in Reflection.Emit, addressing issue #120909. Previously, attempting to reference fields or methods with unmanaged function pointer signatures would fail.

Key Changes:

  • Modified signature encoding to properly handle function pointer types by using UnderlyingSystemType for non-function-pointer types
  • Updated member reference handling to retrieve modified types for function pointer fields and method parameters/returns
  • Expanded test coverage to include both fields and methods with various function pointer signatures

Reviewed Changes

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

File Description
AssemblySaveTypeBuilderTests.cs Expanded tests from just fields to include methods with function pointer signatures; uncommented previously broken test cases
SignatureHelper.cs Added logic to use UnderlyingSystemType for non-function-pointer types when writing signatures
ModuleBuilderImpl.cs Enhanced member reference handling to use modified types (GetModifiedFieldType, GetModifiedParameterType) for function pointers

@dotnet-policy-service
Copy link
Contributor

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

@jkotas
Copy link
Member

jkotas commented Oct 28, 2025

The PersistedAssemblyBuilder from the test specifies System.Private.CoreLib as its core assembly, which prevents the runtime from matching the signatures with the ones defined in C# (where the modifier types are from System.Runtime).

I have tried a small standalone test and I do not see a problem with this. Also, I have checked the runtime implementation and it does take assembly forwarding into account, so modopts referencing System.Runtime and System.Private.Corelib are expected to match.

I think it is something else.

@jkotas
Copy link
Member

jkotas commented Oct 28, 2025

Wrt Mono failures: It is ok to disable this test on Mono

@jkotas
Copy link
Member

jkotas commented Oct 28, 2025

I think it is something else.

The problem is that the order of modopts is getting swapped.

The defining assembly has CallConvSuppressGCTransition first:

method unmanaged valuetype [System.Numerics.Vectors]System.Numerics.Vector`1<int32> modopt([System.Runtime]System.Runtime.CompilerServices.CallConvSuppressGCTransition) modopt([System.Runtime]System.Runtime.CompilerServices.CallConvCdecl) *(valuetype [System.Numerics.Vectors]System.Numerics.Vector`1<int32>) field4

The reference has the modopts in opposite order - CallConvCdecl is first:

method unmanaged valuetype [System.Private.CoreLib]System.Numerics.Vector`1<int32> modopt([System.Private.CoreLib]System.Runtime.CompilerServices.CallConvCdecl) modopt([System.Private.CoreLib]System.Runtime.CompilerServices.CallConvSuppressGCTransition) *(valuetype [System.Private.CoreLib]System.Numerics.Vector`1<int32>) [System.Reflection.Emit.Tests]System.Reflection.Emit.Tests.ClassWithFunctionPointerMembers::field4

The order must match.

@jgh07
Copy link
Contributor Author

jgh07 commented Oct 28, 2025

I am currently looking into it more thoroughly, of course using the modified type only when the type itself is a function pointer was too conservative of a condition.

I switched to using modified types all the time, but I stumbled on some strange behavior regarding generics:

using System;
using System.Reflection;

// Generic type left open on purpose
FieldInfo field = typeof(Container<>).GetField("Field");

Console.WriteLine(field.FieldType.GetFunctionPointerReturnType().IsGenericParameter);
Console.WriteLine(field.GetModifiedFieldType().GetFunctionPointerReturnType().IsGenericParameter);

// Output:
// True
// False

public unsafe class Container<T>
{
    public static delegate*<T> Field;
    public static delegate* unmanaged[Cdecl]<T, U> Method<U>() => null;
}

To me this makes no sense, or am I overseeing something? The type being modified should not interfere with IsGenericParameter. This caused a bunch of other tests dealing with generics to fail as well.

@jkotas
Copy link
Member

jkotas commented Oct 28, 2025

To me this makes no sense, or am I overseeing something?

Looks like a bug in the ModifiedType implementation

@jgh07
Copy link
Contributor Author

jgh07 commented Oct 29, 2025

Works now for all cases covered by tests. None of the Reflection.Emit APIs implement GetModifiedFieldType/GetModifiedParameterType, so in those cases I just used the regular type, which still works if the field/method is constructed correctly.

I imagine there might be some edge cases, such as when you use a type parameter of a generic TypeBuilder in a function pointer signature. But I failed to get an example running, constructing function pointers with generic parameters through the reflection API is very hacky and unpleasant.

@jkotas
Copy link
Member

jkotas commented Oct 30, 2025

None of the Reflection.Emit APIs implement GetModifiedFieldType/GetModifiedParameterType

What would it take to implement these APIs on these types? Special casing the Reflection.Emit APIs by type name does not look good.

@jgh07
Copy link
Contributor Author

jgh07 commented Oct 30, 2025

None of the Reflection.Emit APIs implement GetModifiedFieldType/GetModifiedParameterType

What would it take to implement these APIs on these types? Special casing the Reflection.Emit APIs by type name does not look good.

For the new implementation (FieldBuilderImpl) the only issue is that the internals of System.Private.CoreLib are not accessible. In this case simply returning the regular field type works for function pointers, but it would lose all modifiers for "normal" modified types. I looked at using the same TypeDelegator trick that I used to construct the function pointers, but I'm not sure yet how to store the calling convention, if it is not encoded via modopts.

As for the types defined in System.Private.CoreLib (e.g. FieldOnTypeBuilderInstantiation), I have not looked at those, but I imagine it could look similar to the implementation in RtFieldInfo.


public override Type[] GetOptionalCustomModifiers() => _optionalCustomModifiers ?? Type.EmptyTypes;

public override Type GetModifiedFieldType() => FieldType;
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 works for function pointers, but is it acceptable? It doesn't behave how a user might expect (although it didn't work at all until now, so there's that).

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

Labels

area-System.Reflection.Emit 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.

Reflection.Emit unable to reference function pointers with unmanaged calling convention

2 participants