dart-archive / wasm

Utilities for loading and running WASM modules from Dart code
https://pub.dev/packages/wasm
BSD 3-Clause "New" or "Revised" License
310 stars 24 forks source link

Configure Wasmer engine to use: Singlepass, Cranelift, (LLVM) #60

Open rhobro opened 2 years ago

rhobro commented 2 years ago

The way that wasm_bindgen in Rust can access functions that are defined externally, how does one allow the WASM to call dart functions?

liamappelbe commented 2 years ago

When you're instantiating the module, you can fill any function imports with Dart functions:

var inst = (WasmModule(data).builder()
      ..addFunction('env', 'myFunction', (int x) {
        // Do stuff
      }))
    .build();

You can see an example here: https://github.com/dart-lang/wasm/blob/main/wasm/test/fn_import_test.dart

On the C side, you just declare the function, but don't give it a body (this would usually give you a linker error, but you can tell your compiler to treat it as a function to be filled in later).

void myFunction(int x);
rhobro commented 2 years ago

That's brilliant. I'll try that out.

To save creating another issue for each question, I hope it is ok if I put them here. I'll edit the title accordingly.

As far as I know, this plugin uses Wasmer to run the code. Looking at the Wasmer docs, I found that it is possible to use the Singlepass, Cranelift or LLVM compiler in increasing order of runtime performance and compilation time. How can I configure this through the WASM plugin?

In my use case, I have a WASM file which acts as a package. I need to write a Rust program to interact with this package. For this, I plan to use Wasmer rust. I need to then compile the Rust program to WASM which will then run on the phone using this plugin. Is there any way I can import the contents of the package WASM file into rust to avoid having nested Wasmer runtimes?

liamappelbe commented 2 years ago

As far as I know, this plugin uses Wasmer to run the code. Looking at the Wasmer docs, I found that it is possible to use the Singlepass, Cranelift or LLVM compiler in increasing order of runtime performance and compilation time. How can I configure this through the WASM plugin?

Right now we use cranelift and there's no way to configure it. You can check out wasm/bin/cargo.toml to see all the config options we're using. Why do you want to configure this?

In my use case, I have a WASM file which acts as a package. I need to write a Rust program to interact with this package. For this, I plan to use Wasmer rust. I need to then compile the Rust program to WASM which will then run on the phone using this plugin. Is there any way I can import the contents of the package WASM file into rust to avoid having nested Wasmer runtimes?

You mean import package:wasm into rust? I don't know of any way to import dart into rust, but I don't do much rust programming. I think you'd still end up with multiple wasm runtimes, one in an .so file, and one in wasm code.

What if you compile your rust program to wasm, without the wasmer runtime, and with api surface between your rust library and wasm module exposed as function imports and exports? Then you could just import the rust and wasm modules into dart using package:wasm, and wire up the function imports and exports through dart. That way you'd only have package:wasm's runtime.

rhobro commented 2 years ago

I want to configure this because the LLVM compiler is supposed to be the highest performing compiler out of them. My use-case requires as much performance as possible. Is it even possible to have this as an option on mobile?

Would it work if I used Wasmer to compile the package wasm into a .so or .dylib or whatever and then import that into Rust and then compile the result into WASM?

That could be a possibility. I'll look into it.

liamappelbe commented 2 years ago

I want to configure this because the LLVM compiler is supposed to be the highest performing compiler out of them. My use-case requires as much performance as possible. Is it even possible to have this as an option on mobile?

I think LLVM would greatly increase your app size, and make your startup time slower, and only give you a modest speed boost. It wouldn't be possible to use this on iOS. I'm not sure if it would be possible on Android (that's a question for the Wasmer folks).

Before you conclude that LLVM is what you want, you should try running a benchmark. Run your library in Wasmer using LLVM and cranelift, and see how fast it runs, and how big the Wasmer binary is. I suspect that cranelift is the best trade-off between binary size and speed, but if you have data that says otherwise I can make this option configurable.

Would it work if I used Wasmer to compile the package wasm into a .so or .dylib or whatever and then import that into Rust and then compile the result into WASM?

If you do that, you'll have 2 copies of Wasmer in your app, one in package:wasm's .so file, and one in your wasm module. Also I don't know if it's even possible. Maybe try asking on the Rust stack overflow page.

rhobro commented 2 years ago

My app is not one that is to be used by a wide audience and is only for my own use so the app size and startup time are not particularly important. Due to the performance critical nature of the app, I'd happily accept a modest speed boost haha. But if it's not possible, then nothing can be done about that. I might create an issue on the wasmer repo regarding this.

Out of interest, why would LLVM not run on iOS?

I'll run some benchmarks once the full custom WASM code is written. In the meantime, would it be possible to provide an option between Singleness and Cranelift anyway? Any customisability is appreciated.

That sounds inefficient, I'll try formulate a better plan. Thank you though.

rhobro commented 2 years ago

Regarding the customisability for the compiler and engine of wasmer, it'll just allow the dev to choose the best option for their needs

liamappelbe commented 2 years ago

Out of interest, why would LLVM not run on iOS?

I think LLVM is going to be generating machine code at runtime, which is not allowed on iOS. But I might be wrong about that. Check with the wasmer team.

If your project is just for you, you might fine it easier and faster to use FFI to load your Rust library. You could still use package:wasm to load the wasm library if you want.

rhobro commented 2 years ago

The project is just for me but I have 2 reasons for wanting to load my rust code through WASM:

  1. This app will just be an interface to a physical device which only works with mobile. The rust code will contain the logic. I want to be able to load in different logic at any time.

  2. It will make development a bit faster because I won't have to recompile the app each time but just recompile the WASM and upload it

It seems like it'll be necessary to have nested wasmer runtimes for now but I'll check it out with the wasmer team as you suggested.

liamappelbe commented 2 years ago

I mentioned this before, but I'll elaborate: it's possible to load both your rust module and your wasm module using package:wasm, without needing nested wasm runtimes.

  1. Compile both your modules to wasm
  2. Load them both into Dart using package:wasm
  3. When you want to call from the rust module into the wasm module, do so by exporting the function from the wasm module, and importing the function into the rust module. Then you can use Dart as the bridge:
// Load the wasm module.
var wasmInst = (WasmModule(wasmModuleData).builder()).build();
var myFunction = wasmInst.lookupFunction('myFunction');

// Load the rust module.
var rustInst = (WasmModule(rustModuleData).builder()
      ..addFunction('env', 'myFunction', myFunction))
    .build();

From the Rust library's perspective, the wasm module will just be an external shared library that it imports some functions from. The Rust library won't need to run a wasm runtime. I don't know how you declare function imports in Rust, but the C equivalent would be:

void myFunction(int x);
rhobro commented 2 years ago

Unfortunately, that doesn't work for my use case. But thank you very much anyway.

liamappelbe commented 2 years ago

Making this configurable should be doable. It's just plumbing.