-
Notifications
You must be signed in to change notification settings - Fork 22
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
Function pointers #934
Comments
I know that @TIHan has spent some time thinking about how to make these happen for F#. One of the big sticking points in C# was arriving at the syntax Also tagging as an interop area, though in this case it's more about native interop than C# interop (even if that's a scenario). |
NativePtr module |
This is needed for the |
This is interesting as usages have now filtered into real projects, for example the UnrealCLR plugin where they use function pointers so that there is no pinvoke overhead while using the unreal engine:
|
In F# we don't have Roslyn source generators anyway, so this is a moot point. |
Would be great to have support for this too, would allow to do superb native interop with F# too without the requirement of C# code. |
We already have the delegate definition syntax: type A = delegate of int byref * int inref * outref<int> -> unit Why not transfer this syntax to function pointers?
|
As shown above, the positions of the pseudo-attributes still need a decision. They are not real attributes, rather they are looked up specially like with C#. Unlike C#, |
However, I am unsure whether we should use the attribute syntax for pseudo-and-not-real-attributes. |
One use case for adding this would be Here's a little dump of some of the tests, here with C# (as that supports function pointers). With F# I've gotten very close to this, I'll post my findings once I understand what's happening and why, but was never able to beat Technically, it should be possible for F# to write delegate function calls in a more efficient manner than it currently does, i.e. as fast as The idea is that, with function pointers, F# could eliminate the indirection level and we'd gain a few percentages in high-speed dynamic computing (and perhaps also in other cases). That, plus simply the ability to call function pointers from C#, or to emit |
@abelbraaksma There is still |
@Happypig375 funny you say that, as I was just messaging you in that awesome work you did (#200), where you actually added |
@abelbraaksma I only added functions to convert to and from |
Which may be why it was so rarely used if you can't (trivially) convert in and out of it. Meanwhile, I was looking into fun libraries like this one (https://blog.devgenius.io/inline-assembly-in-f-net-language-6d70ab9f58c1). They are merely prototypes, in this case leveraging the Anyway, my experiments currently just evolved around emitting IL. I did find an optimized way to call this, but lack of |
The need for the use of function pointers in F# programming is vanishingly rare and the implementation cost quite high. I'll close this as "no" - however thank you for the suggestion and the discussion |
@dsyme, could you expand on "implementation cost quite high", giving pointers about implications on the IL code gen being more involved? I think suggestions like this shouldn't be closed, but rather flagged as "Microsoft compiler team is not interested in implementing it", leaving open contribution from a motivated party. I don't seem to grasp there is extraordinary work related to tooling integration for this feature, but mostly on the compiler frontend (syntax to support those declarations) and code generation (outputting the IL). Maybe I'm missing something? |
Also, it seems antithetical to https://twitter.com/dsymetweets/status/1224739090253467648 to relinquish this without giving pointers, and directing arrows that would dereference the implementation in the compiler. |
I did some experimentation on this topic today and, while I wasn't able to get it as fast as calling a function pointer in C#, I was able to make it roughly twice the speed of Seems like
C# Dynamic Function Pointer unsafe public class FnPtrCS
{
private IntPtr dll;
private delegate* unmanaged[Stdcall]<double,double> f2k;
public FnPtrCS(string path)
{
dll = NativeLibrary.Load(path);
f2k = (delegate* unmanaged[Stdcall]<double, double>)NativeLibrary.GetExport(dll, "F2K");
}
public double F2K(double T_F)
{
return f2k(T_F);
}
} F# Dynamic Method: [<RequiresExplicitTypeArguments>]
let private getDelegate<'TDelegate> dll name =
let p = NativeLibrary.GetExport(dll, name)
Marshal.GetDelegateForFunctionPointer<'TDelegate>(p)
[<UnmanagedFunctionPointer(CallingConvention.StdCall)>]
type private F2K = delegate of float -> float
[<RequiresExplicitTypeArguments>]
let sF1<'T0, 'TReturn> name callConv (fnPtr: nativeint) =
let dyn = DynamicMethod(name, typeof<'TReturn>, [| typeof<'T0> |])
let il = dyn.GetILGenerator()
il.Emit(OpCodes.Ldarg_0)
if Environment.Is64BitProcess then
il.Emit(OpCodes.Ldc_I8, int64 fnPtr)
else
il.Emit(OpCodes.Ldc_I4, int fnPtr)
il.EmitCalli(OpCodes.Calli, callConv, typeof<'TReturn>, [| typeof<'T0> |] )
il.Emit(OpCodes.Ret)
dyn.CreateDelegate<Func<'T0, 'TReturn>>()
type FnPtrFS(pathToDll: string) =
let dll = NativeLibrary.Load(pathToDll)
let f2k = NativeLibrary.GetExport(dll, nameof F2K)
let f2kIL = sF1<float,float> "f2kIL" CallingConvention.StdCall f2k
let dF2K = getDelegate<F2K> dll (nameof F2K)
member this.F2KIL(f: float) =
f2kIL.Invoke(f)
member this.F2KDelegate(f: float) =
dF2K.Invoke(f) Benchmarks: [<MemoryDiagnoser>]
type FnPtrs() =
let cs = new FnPtrCS(DllImport.DllName)
let fs2 = new FnPtrFS(DllImport.DllName)
[<Benchmark>]
member __.FnPtrCS() =
let mutable x = 0.
for i in 0 .. 100_000 do
x <- x + cs.F2K(i)
x
[<Benchmark>]
member __.FnPtrDelegate() =
let mutable x = 0.
for i in 0 .. 100_000 do
x <- x + fs2.F2KDelegate(i)
x
[<Benchmark>]
member __.FnPtrDynamicMethod() =
let mutable x = 0.
for i in 0 .. 100_000 do
x <- x + fs2.F2KIL(i)
x
|
@smoothdeveloper I'll take another look. However my gut feeling has always been that this particular emulation of C in F# is just too much - and also an unpleasant sinkhole for C# developers in C# trying to eek out that extra perf. |
@dsyme would you consider putting this on the roadmap (i.e., I'm willing to do the research & RFC if approved)? While I sympathize with the idea that this is a relatively rare feature, it may find its uses in libraries (perf wise, or where delegates are dynamically created, like dynamic methods etc) and in CE design, where the intricacies of the function pointer itself are hidden. Another argument that keeps cropping up from time to time is the ease of consuming code that exposes function pointers. |
@T-Gro @vzarytovskii Do you have thoughts here? Personally I find the thought of putting such a primitive into F# really problematic - I just think it will lead far too many people to try to use this to eek out perf, when really they should be using other techniques (like selective inlining). The problem is people would end up using it in the design of components - e.g. a new That said, there is still a consistent request for it above. Ultimately all that's needed to get the perf gain is some way to emit a |
I am honestly conflicted about it, I understand the desire to have support for it - at least from the interop perspective, and from the perspective of minority of users trying to save all the "nanoseconds" they can. I personally am not a huge fan of this feature - bringing such low level constructs to the language will inevitably lead to people overusing them and designing libraries around them. But then again, I am slightly biased against it, since I have never had use cases for it. |
It would be good to get a set of real use cases and what it takes today to fullfill them, e.g. from the UnrealCLR engine or similar. Since the feature is inherently meant for unsafe contexts, I think it does not have place in F# programming using the full syntactical feature set it has in C#. But a few compiler-known functions in fslib could be the way to go to accomplish use cases which could be presented in this issue. |
If it helps put your minds at ease, my use case is entirely around native interop, in this case calling function pointers provided by Excel. I have no interest in rewriting core libraries to take function pointers. My current workaround is to have a C# project that holds the function pointer in a struct and exposes it as a managed method to other internal consumers that provide a memory safe API to public consumers: public unsafe readonly struct Excel12Proc
{
readonly delegate* unmanaged[Stdcall]<int, int, nint, nint, int> _pfn;
public Excel12Proc(nint pfn)
{
_pfn = (delegate* unmanaged[Stdcall]<int, int, nint, nint, int>)pfn;
}
public int Invoke(int xlfn, int count, nint ppOpers, nint pOperRes) => _pfn(xlfn, count, ppOpers, pOperRes);
} This struct and the ability to consume the source generator "CsWin32" are the only uses of C# that I have in the solution. It's definitely not a dealbreaker, but it would be nice if these interop gaps could be filled by F# too. If the feature were in F#, it would also allow me to use typed pointers and avoid some casting at the call site: let code =
excel12v.Invoke(
xlFn,
length,
NativePtr.toNativeInt<nativeptr<xlOper12>> pFnParams,
NativePtr.toNativeInt<xlOper12> &&resp
)
|> enum<xlRetCode> |
I'm wondering about a compiler-known intrinsic that can be used like this: NativePtr.calli<int * int * nativeint * nativeint, int, StdCall> _pfn (xlFn,
length,
NativePtr.toNativeInt<nativeptr<xlOper12>> pFnParams,
NativePtr.toNativeInt<xlOper12> &&resp) Not sure about the module NativePtr =
val inline calli<'Args, 'Ret, 'CallingConvention> : nativeint -> 'Args -> 'Ret
Here |
If something like that would do the job then yes, approve-in-principle. |
My personal use case is with an optimized dynamic library, where I found that using function pointers to native functions yielded a significant benefit when called from static contexts. Consider a scenario that cannot be inlined (like a dynamic method). If the body is small, the overhead of the dynamic method call is significant compared to calli. For my specific case, I couldn't find a workaround, other than using C#. I understand that this is a niche. I don't think anybody here suggests to use function pointers as overloads to F# Core lib functions. Introducing byref etc didn't lead to such proliferation either, right? Thanks for looking into this again, @dsyme. I can put in the work for an RFC on the library functions, see if we can solve this without a language or compiler change. |
For my use I think that would definitely do the job. It would be great to allow all
Regarding |
OP here: it's been a while, and I would now say that all the work that has gone into supporting |
Function pointers
I propose we offer mechanisms to create, consume, and invoke function pointers, along the lines of the new constructs available in C# 9.0: https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/function-pointers.md
The existing way of approaching this problem in F# is nonexistent. It may be possible to write a separate C# assembly when these features are needed and call methods in that assembly from F#, but in most cases this would probably introduce additional burdens of benchmarking and IL code inspection to verify whether the cost of this indirection negates the benefit of faster invocation of the functions referenced via function pointers in the C# code.
Pros and Cons
The advantages of making this adjustment to F# are expanding the opportunities for writing comprehensible and maintainable code that generates high-performance functionality at runtime without resorting to here-be-dragons features of .NET like ILGenerator. F# arguably offers the most pleasant experience in the .NET language ecosystem for this style of development, so it would be nice to have feature parity with C# in writing code that compiles to faster IL opcodes.
The disadvantages of making this adjustment to F# are exposing new potentially unsafe constructs in a language that doesn't have
unsafe
blocks like in C#. But such constructs already exist and it is considered the responsibility of programmers and library users to be aware of the implications of their use.Extra information
Estimated cost (XS, S, M, L, XL, XXL): M -- although this may be a tricky new feature for F#, the C# compiler may provide a starting point for a syntax proposal, a reference implementation, and possibly some reusable code. It's not clear whether these features are fully finalized as of the C# 9.0 release, so we may want to wait until they are considered completely finished to take advantage of the C# team's code, experience, and documentation.
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: