Skip to content

[ffi] Implement leaf FFI calls #36707

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

Closed
4 tasks
sjindel-google opened this issue Apr 23, 2019 · 22 comments
Closed
4 tasks

[ffi] Implement leaf FFI calls #36707

sjindel-google opened this issue Apr 23, 2019 · 22 comments
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. library-ffi type-enhancement A request for a change that isn't a bug

Comments

@sjindel-google
Copy link
Contributor

sjindel-google commented Apr 23, 2019

Add support for FFI leaf calls:

  • No need to enter/leave safepoint - avoid few locked instructions
  • No need to create exit frame or update Thread state
  • No need to block all registers; callee-saved registers can still be live across the call
  • Replace existing InvokeMathCFunction with FFICall
@sjindel-google sjindel-google added library-ffi area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. labels Apr 23, 2019
@mraleph
Copy link
Member

mraleph commented Apr 23, 2019

Additional benefit is that you can pass some pointers down directly (of course assuming that native side is not going to store them somewhere), e.g. pass an internal pointer into typed data arrays or strings, because you know that GC can't move them. For example this opens possibility to just call memcpy to quickly copy memory from typed array or a string into some other place.

@ghost
Copy link

ghost commented Nov 30, 2020

Some informal benchmarking suggests that replacing functions like dart:math's sqrt and sin with FFI native calls, result in a run time of about 200%, compare to the current implementation.

@dcharkes
Copy link
Contributor

Some informal benchmarking suggests that replacing functions like dart:math's sqrt and sin with FFI native calls, result in a run time of about 200%, compare to the current implementation.

That is without implementing leaf-calls I presume @cskau-g.

It might be worth exploring implementing leaf calls. Both for dart:math, as well as for example exposing the inner pointers of TypedData as <...>* in FFI trampolines (StackOverflow).

@ghost
Copy link

ghost commented Dec 14, 2020

Exactly. My previous comment was to further back the need for leaf-calls.

@mkustermann mkustermann added the type-enhancement A request for a change that isn't a bug label Dec 16, 2020
@mkustermann mkustermann assigned ghost Jan 5, 2021
@mkustermann
Copy link
Member

As discussed in todays meeting we might want to use optional parameter: DynamicLibrary.lookupFunction<..>(..., isLeaf: true) Pointer<NativeFunction<...>>.asFunction(..., isLeaf: true).

@mkustermann
Copy link
Member

Let's keep this issue open until the register spilling subtask has also been implemented.

@mkustermann mkustermann reopened this May 21, 2021
@Keithcat1
Copy link

Is there any planned support for allowing to pass pointers to typed data to C functions when doing a leaf call?
Might also be nice to allow passing pointers to Dart integers at least when the expected native type is Pointer, instead of implementing #42509

@mraleph
Copy link
Member

mraleph commented Mar 17, 2022

@Keithcat1

Is there any planned support for allowing to pass pointers to typed data to C functions when doing a leaf call?

Yes, we want to support that eventually. It's an important functionality.

Might also be nice to allow passing pointers to Dart integers at least when the expected native type is Pointer, instead of implementing #42509

I am not sure what you mean here. Dart integers are immutable and (often) unboxed so pointer-to-an-integer does not really have a well defined meaning.

@Keithcat1
Copy link

Keithcat1 commented Mar 19, 2022

What I mean is that it's fairly common for a C library to expect something like a Pointer or some other integer type, and the only way Dart can allocate such a pointer currently is with malloc, whereas C libraries tend to just allocate those integers on the stack.
Whenever I'm encountering this problem, I use:

static late final _floatPtr = ffi.malloc<Float>();

Even though it's unsafe, I wish there was a way to allocate memory on the stack.
Or maybe, if Dart functions ever support returning multiple values, it might be possible to write code like:

final lib = DynamicLibrary.open("mylib.dll");
final nativeFunctionThatNeedsIntPointer = lib.lookupFunction<Void Function(Pointer<Uint32>), (void, int) Function()>("add_inplace")

This code tells the FFI that instead of the Dart function accepting a Pointer as expected, Dart should allocate a temporary Uint32 on the stack whenever this function is called, pass a pointer to that Uint32 to the add_inplace, then return whatever value is now in the integer. This is safer than allocating memory on the stack, but gives you less control. Not that FFI is ever truly safe to begin with.

@mraleph
Copy link
Member

mraleph commented Mar 21, 2022

@Keithcat1 I see what you mean. You want a pointer to the variable more or less, just like it is possible in C.

Yeah, I also found myself wanting something like that when working with APIs which involve output parameters. This request is tracked by #42509 as you point out yourself. I don't think it is in any way tied to leafness of the call.

@Keithcat1
Copy link

Will I only be able to pass a Typed data pointer to C when using a leaf call, or can I also do this in a non-leaf call? Also, how would it work?

@ghost
Copy link

ghost commented Mar 23, 2022

Will I only be able to pass a Typed data pointer to C when using a leaf call, or can I also do this in a non-leaf call?

Anything you can do with a leaf call you can do with a non-leaf call.

Leaf calls is an optimisation where the VM foregoes some of the safety measures for the sake of speed.

@dcharkes
Copy link
Contributor

Are we not guaranteed GC doesn't move objects in a leaf call? In that case we can do more in a leaf call than a non-leaf call.

@ghost
Copy link

ghost commented Mar 23, 2022

Hm. That's a good question. I don't know if we strictly guarantee that as a part of the contract with leaf calls (and promise that that will never change)?

I believe the "sales pitch" for leaf calls from the user's perspective so far has been what I mentioned above.

@mraleph
Copy link
Member

mraleph commented Mar 23, 2022

I don't know if we strictly guarantee that as a part of the contract with leaf calls?

Yes, we do. We don't enter a safepoint around leaf calls - which means GC can't happen inside. That's the whole idea behind leaf calls. User is also not allowed to call back into Dart (hence "leaf" in the name).

That's highlighted in the comment above: #36707 (comment)

Will I only be able to pass a Typed data pointer to C when using a leaf call, or can I also do this in a non-leaf call? Also, how would it work?

Only on leaf calls. As explained above we can't allow that on non-leaf calls.

@Keithcat1
Copy link

What would the syntax for passing a typed data pointer be?

@mraleph
Copy link
Member

mraleph commented Mar 24, 2022

What would the syntax for passing a typed data pointer be?

If I were to make a suggestion then I'd suggest the following rule: if parameter in the native signature is Pointer<T> and parameter in the Dart signature is subtype of TypedData and function is leaf then callee receives pointer to typed data storage rather than handle to the typed data itself.

So something like this:

 final memcpy = DynamicLibrary.process().lookupFunction<
    Pointer<Void> Function(Pointer<Void>, Pointer<Void>, Size),
    Pointer<Void> Function(TypedData, TypedData, int)>('memcpy', isLeaf: true);

final size = 1024*1024;
final a = Uint8List(size);
final b = Uint8List(size);
memcpy(a, b, size);

@dcharkes
Copy link
Contributor

That prohibits passing Pointers. Maybe we should consider introducing a base class in Dart land that has TypedData and Pointer as subtypes. (In the VM we have PointerBase.)

@Keithcat1
Copy link

I don't really understand why typed data pointers can't be passed when doing a non-leaf call. Go lets you pass slice pointers, provided the C function only holds on to the pointer until it returns.
Is it because the typed data could be garbage collected while the C function call is in progress? Then just keep a reference to it until the C function call is done, right?

@dcharkes
Copy link
Contributor

dcharkes commented Apr 5, 2022

@Keithcat1 during a non-leaf call, the Dart GC might move a TypedData to another location in memory.

If what you say about Go is correct, then Go does not run the GC during a call to C. This is similar to what we have for leaf calls. We expect leaf calls to be short, so we don't want to waste cycles on telling the GC that it can run. But non-leaf calls can be long-running, so we there we would like the GC to be able to run. Does that answer your question?

@mraleph
Copy link
Member

mraleph commented Apr 13, 2022

If what you say about Go is correct, then Go does not run the GC during a call to C.

FWIW Go GC is currently non-moving.

Even in moving GC you can make this work but it requires GC to support object pinning (support situations when certain objects can't move because they are pinned until after they are unpinned). Object pinning is a complexity that we want to avoid as much as possible (at least with the current GC architecture which assumes that any object in new space can be moved freely).

@dcharkes
Copy link
Contributor

Let's keep this issue open until the register spilling subtask has also been implemented.

I believe this was addressed in ba341d7.

For extra features, or more optimizations for leaf calls please open new issues.

Related open issues:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. library-ffi type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

5 participants