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

Design and implement Dart VM FFI #34452

Open
mraleph opened this issue Sep 12, 2018 · 118 comments
Open

Design and implement Dart VM FFI #34452

mraleph opened this issue Sep 12, 2018 · 118 comments

Comments

@mraleph
Copy link
Contributor

@mraleph mraleph commented Sep 12, 2018

The present feature tracks the implementation of Dart FFI support enabling interop with C & C++ code from Dart.

Status

The feature is in beta. More details here:
https://dart.dev/guides/libraries/c-interop

For any discussion of this feature, or feedback or questions regarding the feature, kindly join and post the dart-ffi group: https://groups.google.com/forum/#!forum/dart-ffi

Background

Some inspirational references:

@lrhn
Copy link
Member

@lrhn lrhn commented Sep 14, 2018

A general FFI could also be useful for interaction with JavaScript, so we should consider whether it's possible to make something general wihout compromising on the usability on each platform.

The current JS-interop functionality is not a clean design, it should be possible to improve it.

@matanlurey
Copy link
Contributor

@matanlurey matanlurey commented Sep 14, 2018

@lrhn:

The current JS-interop functionality is not a clean design, it should be possible to improve it.

I would not assume that without talking to @vsmenon. The syntax might leave a little to be desired, but I have no desire to make a breaking change to the syntax unless it vastly improves end users, not just the fact it isn't a "clean design".

@vsmenon
Copy link
Member

@vsmenon vsmenon commented Sep 14, 2018

Providing a new syntax / library doesn't necessarily imply removing the existing support. With that in mind, I think @lrhn 's suggestion is good.

@yjbanov
Copy link

@yjbanov yjbanov commented Oct 18, 2018

@lrhn @vsmenon I think C/C++-oriented interop makes sense on the Web in the context of WebAssembly. I wouldn't sacrifice native FFI capabilities due to JS. It's fine to have two systems that work best for their respective constraints.

@krisgiesing
Copy link

@krisgiesing krisgiesing commented Nov 27, 2018

Is this referring to Dart calling C/C++ interfaces or vice versa?

@mraleph
Copy link
Contributor Author

@mraleph mraleph commented Nov 27, 2018

@krisgiesing Initially it will be Dart calling C, later we would like to extend this to C calling Dart.

Though it is very hard to draw a line because we plan to support callbacks as part of the initial offering - so C would be able to call back into Dart.

Here is the markdown version of the vision doc (implementation is currently being prototyped).

Let me know if you have any comments on that one.

@matklad
Copy link

@matklad matklad commented Nov 28, 2018

This potentially requires using conditional imports.

Conditional compilation is a pain for IDEs. It's worth looking into how Kotlin handles a similar problem with expect/actual declarations on a language level. In a nutshell, expect/actual means that you write a single header/interface file, and a corresponding implementation for each platform. Because the interface is shared between all platforms, IDE does not need to know the "current" platform to do IDE stuff.

@mraleph
Copy link
Contributor Author

@mraleph mraleph commented Nov 28, 2018

@matklad the expect/actual stuff is pretty close to interface libraries that were discussed in context of the conditional imports long time ago - exactly to solve IDE problem. However I don't think this went anywhere - and I think conditional imports are pretty much dead.

I think in the context of FFI we will be implementing what Structure Layouts and Portability describes.

I will amend the section that still talks about conditional imports.

@29e7e280-0d1c-4bba-98fe-f7cd3ca7500a
Copy link
Contributor

@29e7e280-0d1c-4bba-98fe-f7cd3ca7500a 29e7e280-0d1c-4bba-98fe-f7cd3ca7500a commented Dec 24, 2018

Any updates for dart lang FFi ? very happy if there have usable FFI .
We can import c/cpp language lib with it.

@dcharkes
Copy link
Contributor

@dcharkes dcharkes commented Dec 26, 2018

@netroby I'm working on it. We plan to add an initial version during Q1. So far the prototype closely follows the vision doc. Let me know whether that covers your use case.

@fimbault
Copy link

@fimbault fimbault commented Jan 8, 2019

Seems great, calling C is a must have. Is there a way to get access to the proto? Thanks.

@mraleph
Copy link
Contributor Author

@mraleph mraleph commented Jan 11, 2019

@fimbault we plan to release prototype for public consumption later this quarter in few stages: first we will land support for FFI in JIT mode on X64 only, then it will be expanded to cover X64, ARM, ARM64 in JIT and AOT.

@jodinathan
Copy link

@jodinathan jodinathan commented Jan 16, 2019

@matklad the expect/actual stuff is pretty close to interface libraries that were discussed in context of the conditional imports long time ago - exactly to solve IDE problem. However I don't think this went anywhere - and I think conditional imports are pretty much dead.

I think in the context of FFI we will be implementing what Structure Layouts and Portability describes.

I will amend the section that still talks about conditional imports.

are you talking about this: #24581 ?

I use it and it is great.

@robertmuth
Copy link

@robertmuth robertmuth commented Jan 18, 2019

Are there plans to support calling into shared libraries similar to python's ctypes?

@mraleph
Copy link
Contributor Author

@mraleph mraleph commented Jan 18, 2019

@robertmuth yes, please see the doc referenced from #34452 (comment) for more details.

@rootext
Copy link

@rootext rootext commented Jan 24, 2019

It will be great to have FFI in Dart.
From my perspective, FFI includes the following major features:

  1. Load dynamic library by name
  2. Lookup function in library by name
  3. Define native function signature
  4. Map dart types to native types and vice versa (including structs and unions)

There is proposal for all of them here.
Some thoughts:

For 1.
It's proposed to load library by name manually:

final lib = DynamicLibrary.open('libfoo.so');

It is good approach. However there are some alternative ways:

a. Load library manually but return class were all native (or abstract) functions resolved to dynamic library functions:

class Foo extends DynamicLibrary {
  int add(int a, int b) native;
}
final Foo lib = DynamicLibrary.open<Foo>(Platform.isLinux ? 'libfoo.so' : 'foo.dll');
lib.add(1,2); //3

Dart VM lookup all native function in class automatically.
JNA

b. The same as a. but use class name as dynamic library name:

//mapped to libfoo.so on Linux, to libfoo.dylib on macOS and foo.dll on Windows
class Foo extends DynamicLibrary { 
  int add(int a, int b) native;
}
final Foo lib = new Foo();

This automatic mapping is implemented in native extensions: Dart's library name mapped to dynamic library name. Nothing new.

c. The same as a. but use annotation on class:

@ffi.Library('foo')
class MyFoo { 
  int add(int a, int b) native;
}
final Foo lib = new Foo();

If library has different names (for instance OpenGL) than annotation can accept list of names.

@ffi.Library('Opengl32.dll', 'libGL.so.1')
class OpenGL {
}

d. Don't use class and define library name for each function:

@ffi.Library('foo')
int add(int a, int b) native;

C# DllImport.
Dart automatically load dynamic library by name and lookup function (symbol).
Disadvantages: developer doesn't control when dynamic library is loaded/unloaded. At least special API is needed.

e. Use Dart library name as in native extensions
Disadvantages: developer doesn't control when dynamic library is loaded/unloaded, there is no way to define different names.

For 2.
It is OK to be able to lookup function manually.
However, imagine a library with hundreds functions.
Have hundreds lines of code like:

final add = lib.lookupFunction<ffi.Int32 Function(ffi.Int32, ffi.Int32), int Function(int, int)>('add');

isn't great.

There are some other options to lookup function automatically:

a. Lookup by function name:

int add(int a, int b) native; //lookup 'add' function in dynamic library

b. Define name after native keyword as in native extensions:

int myadd(int a, int b) native 'add'; //lookup 'add' function in dynamic library

c. Lookup by name in annotation:

@ffi.Function('add')
int myadd(int a, int b) native; // lookup 'add' function in dynamic library

It works in for cases 1.a-1.c and for 1.d.
Anyway, I believe, it should be a way to load functions automatically based on function signature, which is already defined in my Dart code.

For 3. and 4.
It would be great to have something like this:

int add(@ffi.Type('Int') int a, @ffi.Type('Int') int b)

where annotation @ffi.Type define C type of parameter.

Top C data types should be supported: integer types (char, short, int, long, long long including unsigned), float types, pointers etc.
Dart VM should resolve their sizes in bits automatically based on run-time platform.
It isn't enough to support platform-independent types like int32_t or unit64_t.
It would be inconvenient to define sizes for each platform manually.

Some real world examples:

void srand(@ffi.Type('Uint') int seed); //libc on Linux
@ffi.Type('Ulong') int GetLastError(); //kernel32 on Windows

int SDL_Init(@ffi.Type('Int32') int flags); //SDL cross-platform
void SDL_ShowWindow(@ffi.Type('IntPtr') int window); //SDL cross-platform

For instance, SDL uses both platform-independent (int32_t) and platform-dependent (int, int *) types in API.

For structs it would be great to have automatic struct packing based on field order, its C type and run-time platform.
Instead of:

@ffi.struct({
  'x64 && linux': { // Layout on 64-bit Linux
    'x': ffi.Field(ffi.Double, 0),
    'y': ffi.Field(ffi.Double, 8),
    'next': ffi.Field(ffi.Double, 16)
  },
  'arm && ios': {  // Layout on 32-bit iOS
    'x': ffi.Field(ffi.Float, 4),
    'y': ffi.Field(ffi.Float, 8),
    'next': ffi.Field(ffi.Pointer, 0)
  },
})
class Point extends ffi.Pointer<Point> {
  double x;
  double y;
  Point next;
}

it would be great to write something like:

class Point  {
  @ffi.Type('Double')
  double x;
  @ffi.Type('Double')
  double y;
  @ffi.Type('Pointer')
  Point next;
}

It is enough information for Dart VM to pack structure on each supported platform.
However, ability to define field offset and struct size manually is welcome:

@ffi.StructSize(24)
class Point  {
  @ffi.Offset(0)
  @ffi.Type('Double')
  double x;
  @ffi.Offset(8)
  @ffi.Type('Double')
  double y;
  @ffi.Offset(16)
  @ffi.Type('Pointer')
  Point next;
}

JNA
C#

@truongsinh
Copy link

@truongsinh truongsinh commented Jul 3, 2019

Wanna follow up whether we can expect iOS support by Dec 2019?

@mit-mit
Copy link
Member

@mit-mit mit-mit commented Jul 3, 2019

Wanna follow up whether we can expect iOS support by Dec 2019?

We hope to have that in the preview fairly soon. Please join the email group mentioned in #34452 (comment) to get updates when that happens.

@sjindel-google
Copy link
Contributor

@sjindel-google sjindel-google commented Aug 21, 2019

@Kleak

We've added a new feature to the Pointer api, Pointer.getExternalTypedData. This creates an external TypedData array backed by the C memory from which reads/writes are very efficient.

I modified your benchmark to use this feature, and got the following results:

Before any modifications

The results are similar to what you saw:

LibSodiumSHA256: FooBar
LibSodiumSHA256: 0d749abe1377573493e0df74df8d1282e46967754a1ebc7cc6323923a788ad5c
LibSodiumSHA256(RunTime): 225.35954929577466 us.
LibSodiumSHA512: FooBar
LibSodiumSHA512: 0d6fc00052fee1d3ae1880d5dc59b74dd50449ffdea399449223d2c5b792395ce64153b150fc0fc01bfeed30c7347411cfb8a3b17b51fd8aa6c03acfbcd09e7b
LibSodiumSHA512(RunTime): 420.7172907025663 us.
LibSodiumSHA512FFIOnly: FooBar
LibSodiumSHA512FFIOnly: Pointer: address=0x55c12c900c80
LibSodiumSHA512FFIOnly(RunTime): 9.616287064683792 us.

After using getExternalTypedData in CArray.from and CArray.bytes

LibSodiumSHA256: FooBar
LibSodiumSHA256: 0d749abe1377573493e0df74df8d1282e46967754a1ebc7cc6323923a788ad5c
LibSodiumSHA256(RunTime): 20.373880711047725 us.
LibSodiumSHA512: FooBar
LibSodiumSHA512: 0d6fc00052fee1d3ae1880d5dc59b74dd50449ffdea399449223d2c5b792395ce64153b150fc0fc01bfeed30c7347411cfb8a3b17b51fd8aa6c03acfbcd09e7b
LibSodiumSHA512(RunTime): 22.924955869879188 us.
LibSodiumSHA512FFIOnly: FooBar
LibSodiumSHA512FFIOnly: Pointer: address=0x55d2a2cd7200
LibSodiumSHA512FFIOnly(RunTime): 9.691031461839255 us

After using getExternalTypedData and caching the allocated arrays

This optimization should not be needed after we optimize calls to Pointer.allocate and Pointer.getExternalTypedData itself.

LibSodiumSHA256: FooBar
LibSodiumSHA256: 0d749abe1377573493e0df74df8d1282e46967754a1ebc7cc6323923a788ad5c
LibSodiumSHA256(RunTime): 7.746091132671043 us.
LibSodiumSHA512: FooBar
LibSodiumSHA512: 466f6f426172
LibSodiumSHA512(RunTime): 7.551476501704745 us.
LibSodiumSHA512FFIOnly: FooBar
LibSodiumSHA512FFIOnly: 0d6fc00052fee1d3ae1880d5dc59b74dd50449ffdea399449223d2c5b792395ce64153b150fc0fc01bfeed30c7347411cfb8a3b17b51fd8aa6c03acfbcd09e7b
LibSodiumSHA512FFIOnly(RunTime): 6.1046642593988745 us.

I also switched CArray.bytes to use List.from instead of List.unmodifiable because List.unmodifiable triggers a runtime call.

Result

The final performance (which can be achieved with the FFI today) is more than 2x faster than both PointerCastle and Crypto.

@jaumard
Copy link

@jaumard jaumard commented Aug 21, 2019

hey @sjindel-google ! Pretty nice optimization !! Do you mind make a PR of your modifications on the repo ? We would like to integrate those optimizations and as you already have done the migration that would be awesome ^^

@pouloghost
Copy link

@pouloghost pouloghost commented Aug 27, 2019

What's the plan in releasing ffi on flutter? Should I stick to platform channel or should I investigate in exporting native extension myself or just wait for ffi. DESPERATE for calling cpp from dart, these is too much existing code in cpp to rewrite.

@truongsinh
Copy link

@truongsinh truongsinh commented Aug 27, 2019

What's the plan in releasing ffi on flutter? Should I stick to platform channel or should I investigate in exporting native extension myself or just wait for ffi. DESPERATE for calling cpp from dart, these is too much existing code in cpp to rewrite.

From what I understand, we can already use FFI on Android, both debug and release, but the functionality is limited, and API subject to change. I have not found any example on iOS, nor the status of iOS in general.

Ref #37224 (comment)

@sjindel-google
Copy link
Contributor

@sjindel-google sjindel-google commented Aug 27, 2019

@truongsinh is correct, although iOS is supported equally well as Android.
See https://github.com/flutter/flutter/wiki/Binding-to-native-code-via-FFI for details on iOS.

@truongsinh
Copy link

@truongsinh truongsinh commented Aug 27, 2019

@truongsinh is correct, although iOS is supported equally well as Android.
See https://github.com/flutter/flutter/wiki/Binding-to-native-code-via-FFIl for details on iOS.

A small typo in your link, 1 I too much at the and of the link (FFI vs FFII)

@sjindel-google
Copy link
Contributor

@sjindel-google sjindel-google commented Aug 27, 2019

Thanks, corrected.

@truongsinh
Copy link

@truongsinh truongsinh commented Aug 27, 2019

@sjindel-google I was able to create an extremely simple example on iOS

You can see in Swift code I use @_cdecl to declare C interface. Should this be mentioned in the wiki page?

@sjindel-google
Copy link
Contributor

@sjindel-google sjindel-google commented Aug 27, 2019

Sure, feel free to edit the Wiki page!

@truongsinh
Copy link

@truongsinh truongsinh commented Sep 18, 2019

Should #3691 be re-considered in the interest of FFI?

@dcharkes
Copy link
Contributor

@dcharkes dcharkes commented Sep 18, 2019

@WoodyGuo
Copy link

@WoodyGuo WoodyGuo commented Sep 25, 2019

Just went through a simple ffi demo and it worked well.

I have a question though, can i call Java methods from c code which is loaded via ffi on an Android device?

@dcharkes
Copy link
Contributor

@dcharkes dcharkes commented Sep 25, 2019

@WoodyGuo You would have to use JNI for that.

@WoodyGuo
Copy link

@WoodyGuo WoodyGuo commented Oct 8, 2019

@WoodyGuo You would have to use JNI for that.

Thanks Daco for the prompt reply!

So I assume that I must split my single native library into 2 ones.
One is to be loaded from Java via System.loadLibrary, and the other is to be loaded from Dart via FFI.
So what would you suggest to use to call code in one library from the other one?

dart_ffi

@mikeperri
Copy link

@mikeperri mikeperri commented Oct 15, 2019

@WoodyGuo It looks like you can make calls to the same instance of the same native library from both Java and Dart. This cpp file worked for me (after calling the first function from Java, calling the second one returns 123 to Dart):

extern "C" {
    int32_t setByJava = 0;

    JNIEXPORT void JNICALL
    Java_com_michaeljperri_flutter_1plugin_FlutterPlugin_native_1setValue(JNIEnv *env, jobject instance) {
        setByJava = 123;
    }

    __attribute__((visibility("default"))) __attribute__((used))
    int32_t get_val_from_java(const char* filename) {
        return setByJava;
    }
}

The cpp file is linked as a shared library. I have a Java class with
System.loadLibrary("my_plugin") and a Dart file with DynamicLibrary.open("libmy_plugin.so").

@WoodyGuo
Copy link

@WoodyGuo WoodyGuo commented Oct 15, 2019

@mikeperri cool, thanks for the sharing.

@scriptsman
Copy link

@scriptsman scriptsman commented Jun 3, 2020

Is C calling Dart supported now?

@mit-mit
Copy link
Member

@mit-mit mit-mit commented Jun 3, 2020

Yes, it's supported, but still not entirely complete. See https://dart.dev/guides/libraries/c-interop

@scriptsman
Copy link

@scriptsman scriptsman commented Jun 4, 2020

@mit-mit Thank you.But it seems that the examples only show how to call C functions in dart.And I want to know if invoking a dart method from the C/C++ code is supported now , just like the end of this page?

@dcharkes
Copy link
Contributor

@dcharkes dcharkes commented Jun 4, 2020

@scriptsman callbacks are supported as well:

  • synchronous callbacks: see samples
  • asynchronous callbacks: see samples

And here and here is the native code for both samples.

@xuchaoqian
Copy link

@xuchaoqian xuchaoqian commented Jun 4, 2020

@dcharkes Looking forward to supporting instance(not class) method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Dart VM FFI
  
1.0
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.