-
Notifications
You must be signed in to change notification settings - Fork 21
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
Emit direct delegates when possible #1083
Comments
One problem with the above is that in the case of a multi-argument delegate there's no simple way to always get a direct delegate Consider this code: type C() =
static method M(arg1: int, arg2: int) = 1
System.Func<int,int,int>(C.M) This doesn't compile - the problem is that F# asks delegates to be implemented using curried functions, so an explicit lambda is needed: type C() =
static method M(arg1: int, arg2: int) = 1
System.Func<int,int,int>(fun a b -> C.M(a,b)) Per the above spec, a direct delegate would only be generated for release code, but not debug code. We could in theory always generate a direct delegate here even for debug code though it would be a little unusual because you would then no longer be able to breakpoint inside the lambda on the call to Note there are situations where C# APIs actually do care about the names of arguments and characteristics of target methods - they use inspection to do various things and it can even effect data decoding and deserialization. As far as I understand things this includes the ASP.NET Core Minimal WebAPI implementation. |
I'll mark this as approved in principle, though there is one technical question remaining as mentioned above. |
As an aside, it should be noted that delegate construction always delays (and reevaluates) the relevant function on each call, so, for example new System.Action<int>(failwith "") is equivalent to new System.Action<int>(fun arg -> (failwith "") arg) and doesn't fail on delegate construction, but rather delegate invocation. |
Notes on the testing matrix for this
|
Note to self: when creating first-class lambdas for .NET methods we aren't giving good names to implied arguments for the lambda. This can be improved: > System.IO.File.OpenText;;
val it : arg00:string -> System.IO.StreamReader |
Emit direct delegates might be useful with mono p/invoke callbacks: We are interested in this code: CaptureDelegate d = MyCapture;
block_value.SetupBlock(d, null);
...
[MonoPInvokeCallback(typeof(CaptureDelegate))]
static void MyCapture(IntPtr block, IntPtr self, IntPtr uiView)
{
... This code can be written in F# as follows: let d = CaptureDelegate(MyClass.MyCapture)
block_value.SetupBlock(d, null) but this throws an exception at runtime: Perhaps this is incorrect behavior of the compiler and compiler should add attributes to the method of closure object. @dsyme what do you think? I found a way to create "direct" delegate via reflection: let d = Delegate.CreateDelegate(typeof<CaptureDelegate>, typeof<MyClass>.GetMethod("MyCapture"))
block_value.SetupBlock(d, null) and this code works well. But this looks like a dirty hack. |
I will try to fix it one day; don't know when I will have time. |
Considering the parameter name issue reported in #1145, does that not indicate that debug and release code should stay aligned? The OP says:
but if the user code relying on this inspection for API contracts is the ASP.NET Core framework, that's a whole other order of magnitude of breaking change. |
Relevant minimal api issue dotnet/aspnetcore#38906 Important to note, the one way to add attributes to parameters (for instance for |
Does this suggestion cover dotnet/fsharp#14582? |
Is there any hope this behaviour will be implemented? Because right now, for example, openapi is completely broken in f# minimal api apps |
Sure, It's not planned as for now, but we'll accept RFC and implementation. |
* Override `ToString` on `BuildPhase`. * Cache the delegate passed into `ConcurrentDictionary.GetOrAdd` where possible. See dotnet#14582, fsharp/fslang-suggestions#1083, etc.
* Minor compiler perf improvements * Override `ToString` on `BuildPhase`. * Cache the delegate passed into `ConcurrentDictionary.GetOrAdd` where possible. See #14582, fsharp/fslang-suggestions#1083, etc. * Name * Update release notes * Fmt * Remove unneeded `StringBuilder` * Start count at 1 * Go back to `GetOrAdd` * I don't think I fully understand why, but I did some rough microbenchmarking, and for some reason the `GetOrAdd` and `Interlocked.Increment` on a ref cell technique is actually something like twice as fast as `AddOrUpdate`. --------- Co-authored-by: Petr <[email protected]>
F# currently never emits "direct" delegates - it only ever emits delegates that go via a closure object.
As background, .NET IL permits the emit of direct delegates, e.g. for the code:
F# currently always emits a closure. Instead it is possible to emit
That is, a direct delegate is not a closure but rather a delegate with delegate.TargetMethod known to be the exact MethodInfo for the target.
This needs careful specification and is best done as an RFC as the differences are, in some sense, observable to reflection.
The basic situation is
The cases we care about are where the target is a "known" function or method, for example:
Arguably we may also care about partial applications - here a closure is needed but the argument names generated for the closure could be those drawn from the elided arguments of the obvious target:
The questions are
We have to consider these questions for both Debug and Release code.
The proposal for the cases above is:
Direct delegate?
If no direct delegate, then what are the closure argument names?
Are these changes visible in quotations?
Are these changes visible to FCS?
This is in theory a potential breaking change because user code doing inspection on delegate value .TargetMethod may detect the difference. However within the context of the language spec it's a reasonable change as the TargetMethod is currently a closure and so inspection is not particularly useful, and the language spec gives no guarantees about this situation. For this reason it is reasonable to make this change via an RFC and
/langversion
The transformation would be implemented in the F# optimizer and Ilxgen.
Pros and Cons
The advantages of making this adjustment to F# are efficiency and more direct translation of F# code
The disadvantages of making this adjustment to F# are it's work.
Extra information
Estimated cost (XS, S, M, L, XL, XXL): S
Related suggestions: (put links to related suggestions here)
Affidavit (please submit!)
Please tick this by placing a cross in the box:
Please tick all that apply:
For Readers
If you would like to see this issue implemented, please click the 👍 emoji on this issue. These counts are used to generally order the suggestions by engagement.
The text was updated successfully, but these errors were encountered: