dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.31k stars 1.59k forks source link

[vm/ffi] Explore using `FfiNative` for static linking #49418

Open dcharkes opened 2 years ago

dcharkes commented 2 years ago

Given a dart file

@FfiNative<Int64 Function(Int64, Int64)>("FfiNative_Sum", isLeaf:true)
external int sum(int a, int b);

main(){
  print(sum(40, 2));
}

And a C file:

#include <stdint.h>

uint64_t FfiNative_Sum(uint64_t a, uint64_t b) { return a + b; }

It should be possible to compile this to a pc relative call if we use the native linker.

  1. Compile the dart program to a (a) an assembly file with a call FfiNative_Sum or (b) to an ELF file with relocation records (a static library in ELF format rather than a dynamic library in ELF format).
  2. Compile the C program to a relocatable file (static library or object file).
  3. Link them together with the native linker to a dynamic library in ELF format.
  4. Run with dartaotruntime passing in the dynamic library or appending it.

There is a huge design space.

For example we should add asset tags to disambiguate native symbols. And we could use the same syntax for dynamic linking using these assets. I will not write these up here, but wanted to have a bug to point to.

safasofuoglu commented 2 years ago

Would this enable LTO inlining of FfiNative_Sum?

dcharkes commented 2 years ago

Would this enable LTO inlining of FfiNative_Sum?

I believe so.

mraleph commented 2 years ago

Unfortunately, LTO inlining requires some interoperability between compilers (e.g. either us using the same IR as C/C++ compiler or us implementing C/C++ compiler on top of our IR). Currently we have no concrete plans for going that route.

The best we will get here is a direct (e.g. PC-relative) call from Dart function to C function, but not the inlining of C function into the Dart function.

If you have some C code that is so small that it hugely benefits from inlining into its Dart caller then probably that C code should be written in Dart if possible. Though, I can understand situations where callee is extremely low level (e.g. a snippet of assembly code). I have been dreaming about adding inline assembly to dart:ffi - but that's a very speculative and probably far out, given our current priorities.

dcharkes commented 2 years ago

Unfortunately, LTO inlining requires some interoperability between compilers (e.g. either us using the same IR as C/C++ compiler or us implementing C/C++ compiler on top of our IR).

Today I learned, with LTO a higher level representation (LLVM bitcode) is put in object files, not ELF:

(I assumed LTO works on regular ELF object files and static libraries. ELF object files and static libraries only contain symbols and machine code, not a higher level representation. In which case LTO would need to prove to itself which registers are preserved to enable inlining.)

modulovalue commented 1 year ago

FYI: A week ago, at WWDC23, Apple introduced the concept of mergeable libraries that, they claim, combine the best parts of static and dynamic libraries. The main selling point appears to be improved build times. (Maybe there's something there that could be used for inspiration to e.g. minimize the impact of static linking on e.g. hot reload.)

dcharkes commented 1 year ago

FYI: A week ago, at WWDC23, Apple introduced the concept of mergeable libraries that, they claim, combine the best parts of static and dynamic libraries. The main selling point appears to be improved build times. (Maybe there's something there that could be used for inspiration to e.g. minimize the impact of static linking on e.g. hot reload.)

I believe we will solve this with https://github.com/dart-lang/sdk/issues/50565 already. If your build.dart can both do static and dynamic libraries, the embedder (dart standalone or Flutter) can decide per command to have prefer-dynamic or prefer-static. In debug mode it can then use dylibs to support hot reload, and when making a release build for the app it could use static linking.

That being said, maybe mergable libraries will be a 3rd type link mode for assets, that build.dart could produce and that can be consumed both as dynamic library and static library.

https://github.com/dart-lang/native/blob/2de88135e25577d851c95b04af0ceb74b706f285/pkgs/native_assets_cli/lib/src/model/link_mode.dart#L10

dxvid-pts commented 10 months ago

If we know which functions are called in the native library, couldn't this also enable three-shaking of the native library? E.g if it is a huge library like OpenSSL but the user only calls one function of it, we could strip the rest to make binary size smaller. This would be amazing!

dcharkes commented 10 months ago

If we know which functions are called in the native library, couldn't this also enable three-shaking of the native library? E.g if it is a huge library like OpenSSL but the user only calls one function of it, we could strip the rest to make binary size smaller. This would be amazing!

That's precisely the point! :)

medz commented 1 day ago

I think the most confusing point is that I use C to write a call to a function in a static library. Then I can find all the functions of this static library from Dart FFI, otherwise I can't get any.

For the problem of finding the functions exposed by the .a static library, I now write a C file that does nothing but forward parameters to the functions of the static library.