Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

Discussion: Permit static lambda assignment to function pointers #3476

Closed
alrz opened this issue May 21, 2020 · 16 comments
Closed

Discussion: Permit static lambda assignment to function pointers #3476

alrz opened this issue May 21, 2020 · 16 comments

Comments

@alrz
Copy link
Contributor

alrz commented May 21, 2020

It should be safe to pass a static lambda to a function pointer.

public static unsafe class C {
    static bool Any<T,A>(this T[] array, delegate*<T, A, bool> predicate, A arg) => false;

    public static void M(int a, int b) {  
        new []{1}.Any(&static (item, t) => item == t.a, (a, b));
    }

    // instead of
    public static void M(int a, int b) {  
        static bool predicate(int item, (int a, int b) t) => item == t.a;   
        new []{1}.Any(&predicate, (a, b));
    }
}

Otherwise you must fallback to using local function and declare everything explicitly.

@RikkiGibson
Copy link
Contributor

This requires a new feature which amends these parts of the 'static lambdas' speclet:

No guarantee is made as to whether a static lambda definition is emitted as a static method in metadata. This is left up to the compiler implementation to optimize.

We will now need to guarantee that a 'static' lambda is emitted as 'static' if its address is taken.

Removing the static modifier from a lambda in a valid program does not change the meaning of the program.

Removing the 'static' modifier in a context where it is converted to function pointer will introduce a compile-time error.

@333fred
Copy link
Member

333fred commented Jul 7, 2020

We will now need to guarantee that a 'static' lambda is emitted as 'static' if its address is taken.

Presuming, of course, that we would want to make those changes. We explicitly did not make any guarantees about how static lambdas are emitted, and I don't find this feature compelling enough to change that.

@alrz
Copy link
Contributor Author

alrz commented Jul 8, 2020

I don't find this feature compelling enough to change that.

To me, this feels like a feature gap in the language, something that you would expect to be possible but it doesn't work merely because it's not implemented. But I'd agree that it's not very common because function pointers hasn't been shipped yet ;)

@333fred
Copy link
Member

333fred commented Jul 8, 2020

something that you would expect to be possible but it doesn't work merely because it's not implemented

That's not how I think about this at all. Function pointers aren't a common use case. They require unsafe, and getting a pointer requires taking the address of a static method. I wouldn't expect them to work with anything else.

@alrz
Copy link
Contributor Author

alrz commented Jul 8, 2020

They require unsafe, and getting a pointer requires taking the address of a static method.

Within that context, one could decide to use a function pointer instead of a delegate for performance reasons. Then they need to use a local function anywhere they previously used a (static) lambda. Now we're back where we felt the need for static lambdas in the first place (while static local functions were available).

@alrz
Copy link
Contributor Author

alrz commented Jul 8, 2020

I understand that technically a proper method is required to acquire an address, but this would be a convenient syntactic sugar much like what has been done for in parameters -- create a local on-the-fly and pass a ref of that if the current argument is not addressable.

@AraHaan
Copy link
Member

AraHaan commented Apr 3, 2021

Tbh why not just make the static lambda a private static member in the type then convert that to a function pointer and return it where you need to use it instead of using a local function?

@RikkiGibson
Copy link
Contributor

Wanted to address #6111 (comment) in the original issue (cc @HaloFour)

I think that would also tie the compiler to a specific implementation of how static lambdas are emitted. They currently are instance methods on a singleton instance, but that would be incompatible with the function pointer.

Func<int, int> del = static int (int x) => x;
delegate*<int, int> funcPtr = &static int (int x) => x;

A lambda is only used once, so it seems reasonable that the way it is used could affect the way it is lowered. Why couldn't the compiler emit a non-static method for del, and a static method for funcPtr, for example?

@HaloFour
Copy link
Contributor

@RikkiGibson

A lambda is only used once, so it seems reasonable that the way it is used could affect the way it is lowered.

I'd like to see more of that in general, like if a lambda is only invoked from within the method in which it is declared the compiler could emit it as a local function instead, save an allocation or two.

@alrz
Copy link
Contributor Author

alrz commented May 12, 2022

the compiler could emit it as a local function instead, save an allocation or two.

I'm probably missing something but how it could possibly save any allocations?

@RikkiGibson
Copy link
Contributor

I think @HaloFour's suggestion was to adjust lowering/emit in some cases so that no local variable is introduced and no delegate is actually allocated for the lambda.

Func<int, int> multiply = x => x * 2;
Console.Write(multiply(2));


// equivalent to
int multiply(int x) => x * 2;
Console.Write(multiply(2));

@alrz
Copy link
Contributor Author

alrz commented May 12, 2022

Got it. I thought it has something to do with &static lambdas. The only advantage of that I think is type inference.

@timcassell
Copy link

timcassell commented Sep 11, 2022

Considering this spec

Removing the static modifier from a lambda in a valid program does not change the meaning of the program.`

I would find it useful to be able to directly assign a lambda to a function pointer without & or static, as long as no variables are captured.

My use case is, I have a library that supports old versions of Unity (C# 4), and I conditionally compile with newer C# language features for better performance.

A simplified example similar to what I'm doing:

internal delegate void ConvertDelegate<T>(object asyncResult, ref T storage, int index);

internal Merger<T> : IAsyncResult<T>, IIndexedCallback
{
    internal T _result;
    private ConvertDelegate<T> _converter;
    
    internal Merger(ConvertDelegate<T> converter)
    {
        _converter = converter;
    }
    
    void IIndexedCallback.Callback(object asyncResult, int index)
    {
        _result = _converter(asyncResult, ref _result, index);
    }
    
    T IAsyncResult<T>.Result { get { return _result; } }
}

public static AsyncResult<ValueTuple<T1, T2>> Merge<T1, T2>(AsyncResult<T1> result1, AsyncResult<T2> result2)
{
    var merger = new Merger<ValueTuple<T1, T2>>((object asyncResult, ref ValueTuple<T1, T2> storage, int index) =>
    {
        if (index == 0)
        {
            storage.Item1 = ((IAsyncResult<T1>) asyncResult).Result;
        }
        else
        {
            storage.Item2 = ((IAsyncResult<T2>) asyncResult).Result;
        }
    });
    result1.AddIndexedListener(merger, 0);
    result2.AddIndexedListener(merger, 1);
    return new AsyncResult<ValueTuple<T1, T2>>(merger);
}

...

public static AsyncResult<ValueTuple<T1, T2, T3, T4>> Merge<T1, T2, T3, T4>(AsyncResult<T1> result1, AsyncResult<T2> result2, AsyncResult<T3> result3, AsyncResult<T4> result4)
{
    ...
}

And it would be very convenient for me if I could just change the Merger implementation to this so that the lambda expressions wouldn't have to change to fit function pointers and reduce duplicate code:

#if CSHARP_12_OR_GREATER // Or whichever language version
    private delegate*<object, ref T, int, void> _converter;
    
    internal Merger(delegate*<object, ref T, int, void> converter)
    {
        _converter = converter;
    }
#else
    private ConvertDelegate<T> _converter;
    
    internal Merger(ConvertDelegate<T> converter)
    {
        _converter = converter;
    }
#endif

I know my use case is super niche and this may be unlikely to happen, but I figured I should give my input here.

@MichalPetryka
Copy link

Being able to pass static lambdas to function pointers would be nice, but I must warn you, older Unity versions don't always handle calli properly, especially with IL2CPP.

@timcassell
Copy link

Being able to pass static lambdas to function pointers would be nice, but I must warn you, older Unity versions don't always handle calli properly, especially with IL2CPP.

That's why I use conditional compilation for older Unity versions to just use the regular delegate.

@MichalPetryka
Copy link

delegate* unmanaged[Cdecl]<int, float> d = &static (i) => (float)i;

This could also implicitly use UnmanagedCallersOnly when encountering an assignment to an unmanaged pointer, would be pretty useful with interop scenarios now that I think about it.

@dotnet dotnet locked and limited conversation to collaborators Nov 25, 2022
@333fred 333fred converted this issue into discussion #6746 Nov 25, 2022

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants