Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: MethodInfo.GetMethodFromFunctionPointer(void*) #36363

Closed
Sergio0694 opened this issue May 13, 2020 · 4 comments
Closed

Proposal: MethodInfo.GetMethodFromFunctionPointer(void*) #36363

Sergio0694 opened this issue May 13, 2020 · 4 comments

Comments

@Sergio0694
Copy link
Contributor

Overview

Related to the function pointers proposal for C# 9.

This issue is about adding a new API to System.Reflection.MethodInfo:

namespace System.Reflection
{
    public abstract class MethodInfo : MethodBase
    {
        public static unsafe MethodInfo? GetMethodFromFunctionPointer(void* pointer);
    }
}

This would return the MethodInfo? instance mapping to the managed static method being pointed at by that void* pointer. Users would call this method by passing a delegate* pointer to a static managed method, making sure that the assembly the method belongs to is still loaded into memory. This method would only be used for function pointers pointing to static managed methods.

In practice, it would act as a reverse lookup from getting a managed delegate* pointer from a static method using the & operator on the target method (through the method group syntax).

Sample usage

unsafe class Program
{
    public static void Foo() { }

    void Test()
    {
        delegate*<void> ptr = &Util.Log;

        MethodInfo info1 = MethodInfo.GetMethodFromFunctionPointer(ptr)!;

        // Same as
        MethodInfo info2 = typeof(Program).GetMethod(nameof(Foo))!;
   }
}

Motivation/behavior

It might be useful for developers to be able to inspect targeted methods using reflection, and this API would go well along with the other existing similar APIs (eg. MethodBase.GetMethodFromHandle).

I've included a practical use case example from one of my project just to provide additional context for anyone interested. Putting that into an expandable block to keep the main post more concise.

Practical use case example (tap to expand)

I'm working on a library called ComputeSharp, which lets you write DirectX 12 compute shaders in C#, and then converts them into HLSL code and runs them on the GPU. In particular, shaders have the ability to capture functions (pointing to static methods) to enable some sort of metaprogramming. Here's an example of what a simple compute shader might look like:

public struct ActivationKernel : IComputeShader
{
    public ReadWriteBuffer<float> Tensor;

    public Func<float, float> Activation;

    /// <inheritdoc/>
    public void Execute(ThreadIds ids)
    {
        Tensor[ids.X] = Activation(Tensor[ids.X]);
    }
}

Users might assign that Activation from any static method matching the signature.
There are two main issues with this approach though, in particular:

  1. The C# syntax doesn't actually restrict that assignment to static methods. Users might also assign that from eg. a lambda expression with an associated closure, which is not supported by the library. The workaround for this is simply to check at runtime whether the captured delegate does in fact point to a static method. Having this be a build-time check would be nicer.
  2. Since I need to build a different HLSL shader for each combination of shader type and captured delegate(s), I need to override the GetHashCode method by calling Delegate.Method.GetHashCode for each captured function, which is innefficient due to the use of reflection. If I just had a function pointer instead I could just use the pointer value as the identifier for each captured method.

Also, being able to just use a function pointer would also skip the delegate allocation entirely, which would not be a huge difference on its own, but still nice to have 😄

@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added area-System.Runtime untriaged New issue has not been triaged by the area owner labels May 13, 2020
@jkotas
Copy link
Member

jkotas commented May 13, 2020

If I just had a function pointer instead I could just use the pointer value as the identifier for each captured method.

Note that function pointers are not guaranteed to be unique. Calling ldftn twice against same method may return two different pointers. I do not think you can use the function pointer as identifier for anything.

@Sergio0694
Copy link
Contributor Author

Hey @jkotas - thank you for taking the time to look at this proposal!

Well that point in particular just went out the window then. I'm very curious about why that's the case though, is there some resource/specs/etc. that I can use to read up about this? I assumed the function token would be unique for any given method in a loaded assembly 🤔

Putting that specific detail aside (I might just keep using MethodInfo.GetHashCode there anyway), do you think there's value for this API in general, regarding the other possible use cases? For instance, in my scenario I could still use this to restrict the delegates being captured, and I guess other devs might be interested in being able to do this "reverse lookup" from a pointer for other purposes.

@jkotas
Copy link
Member

jkotas commented May 13, 2020

The ECMA-335 spec hints at this behavior:

Note that the address computed by this instruction can be to a thunk produced specially
for this purpose (for example, to re-enter the CIL interpreter when a native version of the method
isn’t available).

It means that e.g. ldftn can return a thunk when the native version isn't available, and a direct pointer once the native version becomes available.

do you think there's value for this API in general

The reverse lookup would be slow, and supporting it adds extra constrains on the runtime to support a niche scenario. I do not see it as a good tradeoff. If you want to use reflection, stick with reflection objects, without roundtripping through function pointers.

@Sergio0694
Copy link
Contributor Author

I see, yeah that makes perfect sense.
Thank you for your time, appreciate it! 😊

Closing this then, have a nice day!

@ghost ghost locked as resolved and limited conversation to collaborators Dec 9, 2020
@tannergooding tannergooding removed the untriaged New issue has not been triaged by the area owner label Jun 24, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants