A Web Assembly executor for the Dart programming language.
Currently it uses the wasmtime 14.0
or wasmi 0.31
Rust crates for parsing and executing WASM modules. Bindings are created using package:flutter_rust_bridge
.
Pub | Source | Description |
---|---|---|
wasm_run | Wasm executor and utilities | |
wasm_run_flutter | Native libraries to use package:wasm_run in Flutter projects |
|
wasm_wit_component | Wit bindings and code generator for Wasm components | |
compression_rs | Compression, decompression, zip and tar achieves (brotli, lz4, zstd, gzip, zlib, ...) | |
image_ops | Enconding, deconding, transformations and operations over multiple image formats (png, jpeg, ico, gif, bmp, tiff, webp, avif, tga, farbfeld, ...) | |
rust_crypto | Hashes, Hmacs, Argon2 Passwords and AES-GCM-SIV encryption. Digests for sha1, sha2, md5, blake3, crc32 | |
wasm_parser | Wasm, Wat and Wit parser, validator, printer and Wasm component tools | |
y_crdt | y.js (https://github.com/yjs/yjs) Dart port, a CRDT implementation. CRDTs allow for local, offline editing and synchronization of shared data. | |
typesql_parser | SQL parser, AST and visitors | |
typesql | SQL type utilities and helpers for Dart model code generator and query execution | |
typesql_generator | SQL types code generator from .sql files to Dart queries and models |
WasmFileUris
WasmRunLibrary
Feature\Runtime | Wasmtime 14.0 | Wasmi 0.31 | Chrome[1] |
---|---|---|---|
multi_value | ✅ | ✅ | ✅ |
bulk_memory | ✅ | ✅ | ✅ |
reference_types | ✅ | ✅ | ✅ |
mutable_global | ✅ | ✅ | ✅ |
saturating_float_to_int | ✅ | ✅ | ✅ |
sign_extension | ✅ | ✅ | ✅ |
extended_const | ✅ | ✅ | ✅ |
floats | ✅ | ✅ | ✅ |
simd | ✅ | ❌ | ✅ |
relaxed_simd | 🏳 | ❌ | 🏳 |
threads_shared_memory_atomics | 🏳 | ❌ | ✅ |
multi_memory | 🏳 | ❌ | ❌ |
memory64 | 🏳 | ❌ | 🏳 |
component_model | ❌[2] | ❌ | ❌ |
tail_call | ❌ | 🏳 | ✅ |
exceptions | ❌ | ❌ | ✅ |
garbage_collection | ❌ | ❌ | 🏳 |
memory_control | ❌ | ❌ | ❌ |
type_reflection | ✅ | ✅ | 🏳 |
wasi_snapshot_preview_1 | ✅ | ✅ | ✅ |
wasi_nn | ❌[2] | ❌ | ❌ |
wasi_crypto | ❌[2] | ❌ | ❌ |
wasi_threads | ❌[2] | ❌ | ❌ |
We provide package:wasm_run_flutter
to bundle the right binaries for your platform compilation targets.
Platform | Architecture | Runtime[1] |
---|---|---|
Linux | aarch64 x86_64 | Wasmtime 14.0 |
MacOS | aarch64 x86_64 | Wasmtime 14.0 |
Windows | aarch64 x86_64 | Wasmtime 14.0 |
iOS | aarch64 x86_64 aarch64-sim | Wasmi 0.31 |
Android | armeabi-v7a x86 x86_64 | Wasmi 0.31 |
Android | arm64-v8a | Wasmtime 14.0 |
Web | N/A | Browser/Wasmi 0.31 |
For pure Dart application (backend or cli, for example), you may download the compiled dynamic libraries for each platform and specify the ffi.DynamicLibrary
in the WasmRunLibrary.set
function or execute the script dart run wasm_run:setup
(or the function WasmRunLibrary.setUp
) to download the right library for your current platform and configure it so that you don't need to call WasmRunLibrary.set
manually. The compiled libraries can be found in the releases assets of this repository.
For the web platform we provide the same interface but it uses the WASM runtime provided by the browser instead of the native library (you may also use the Wasmi WASM module // TODO: not implemented yet).
We use package:wasm_interop to implement the browser web API. We don't need to provide a custom runtime since the browser already has one.
However, in web browsers there is no support for the WAT format and other queries that you may perform over the WASM modules on native platforms. For example, WASM function type definitions of arguments and results are not provided in most browsers. If you need these features, you may use the compiled WASM module (TODO: Not implemented yet).
We use the wasm-feature-detect JavaScript library for feature detection in the browser. To use this functionality in Dart web applications you will need to add the following script to your HTML:
<script src="https://github.com/juancastillo0/wasm_run/raw/main/packages/wasm_run/assets/wasm-feature-detect.js"></script>
<script type="module" src="https://github.com/juancastillo0/wasm_run/raw/main/packages/wasm_run/assets/browser_wasi_shim.js"></script>
This is also required in Flutter, in web/index.html
, if your code calls any function, or needs any object implemented in browser_wasi_shim
:
<head>
<script src="https://github.com/juancastillo0/wasm_run/raw/main/assets/packages/wasm_run/lib/assets/wasm-feature-detect.js" defer></script>
<script type="module" src="https://github.com/juancastillo0/wasm_run/raw/main/assets/packages/wasm_run/lib/assets/browser_wasi_shim.js" defer></script>
<head>
Support for compiling modules in WAT format. At the moment this is only supported in native platforms.
Parsing WASM and WAT modules to explore the exposed interface.
You may retrieve the names and types of each import and export defined in a WASM module.
At the moment this is experimental and will not work in the Wasmi runtime.
The native implementation uses Rust's rayon crate to execute tasks with a work-stealing scheduler.
To use it you must call WasmInstance.runParallel
with the WasmFunction
to execute and the group of arguments to pass to the different threads.
The web implementation uses web workers for running the functions and a simple task queue for scheduling.
Only shared memories can be accessed when running Wasm functions in workers. Regular memories, tables and globals will be instantiated separately for each worker and can not be easily accessed from the Dart API (imports will not be the same reference in Dart). Function imports will be correctly executed in the main Dart process (requires SharedBuffer support for web browsers). Wasm reference values will not work in the web browser and are not properly tested in native platforms.
The repository's packages/rust_threads_example directory contains an implementation example for a WASM module with shared memories, atomics, thread locals and simd. You may compile it with Rust's Cargo nightly toolchain:
RUSTFLAGS='-C target-feature=+atomics,+bulk-memory,+mutable-globals' \
cargo +nightly build --target wasm32-unknown-unknown --release -Z build-std=std,panic_abort
The threads_test.dart contains a benchmark and multiple usage and configuration examples.
The assets/wasm.worker.js is used as the script for implementing the worker, you may override it with the workerScriptUrl
configuration in WorkersConfig
.
You may also configure the WASM imports are by using the mapWorkerWasmImports
parameter. An example of the script can be found in worker_map_imports.js, it exposes a mapWorkerWasmImports
that receives the imports, the WASM module and other information, and returns the mapped WASM imports object.
We support WASI wasi_snapshot_preview1 through the wasmtime_wasi or wasmi_wasi Rust crates, chosen depending on the target platform.
In the web platform we support WASI modules by using bjorn3/browser_wasi_shim. The file system directories exported by the host are in-memory Maps where the files are represented as Uint8List
buffers. Other APIs, such as time and random are implemented using the JavaScript browser APIs.
Usage within Dart can be found in the wasi_test.dart.
The WASI module used to execute the test is compiled from the rust_wasi_example
Rust project within this repo.
You may compile it with the following commands inside the project's directory:
cargo build --target wasm32-wasi
or, if you have cargo-wasi
installed:
cargo wasi build
Another example of a Wasm module using Wasi is the Dart Wit Component Generator.
We use the bjorn3/browser_wasi_shim JavaScript library that provides a shim around the browser API and an in-memory file system with webBrowserFileSystem
.
You may choose to inherit stdin, stdout and stderr from the current process. Or capture stdout and stderr to execute some custom logic for the stdio exposed by the execution of the specific WASI module.
WasmInstance.stderr
and WasmInstance.stdout
provide the captured Streams.
You may provide a list of directories that the WASI module is allowed to access.
You can pass custom environment variables and program arguments to the WASI module or inherit them from the current process.
The WASI modules can have access to the system clock and randomness.
An experimental Wasm Interface Type (WIT) code generator for Dart can be found in the dart_wit_component directory.
This is a work in progress and we will provide a separate package or additional tools for using Wasm components.
The generator is implemented in Rust and the usage within Dart is implemented using package:wasm_run
. The wit document for the implementation can be found in wit/dart-wit-generator.wit the Rust implementation can be found in the dart_wit_component folder which uses wit-bindgen. This is a good example of a package (or application) using wit components to implement functionalities in Dart. The usage within Dart is implemented as a cli and as a Dart library in lib/generator.dart. The generated Dart code can be found in lib/src/generator.dart.
wasm_wit_component:generate
CLICommand line interface usage:
dart run wasm_wit_component:generate wit/input-file.wit lib/generated-world.dart --watch --no-copy-with
Argument | Kind | Description | Default |
---|---|---|---|
inputWitPath | positional | The path to the wit directory or file | none (Required) |
outputDartPath | positional | The path to the dart file that will be generated | next to the wit file |
watch | named | Whether to watch for changes in the wit path and regenerate | false |
no-default | named | Whether to set to false other generator configurations | false |
json-serialization | named | Whether to generate toJson and fromJson | true (false if --no-default) |
to-string | named | Whether to generate toString method | true (false if --no-default) |
equality | named | Whether to generate equality and hashCode | true (false if --no-default) |
copy-with | named | Whether to generate copyWith | true (false if --no-default) |
API usage examples can be found in the wit_generator_test.dart.
Another example with multiple tests can be found in the example directory inside the wasm_wit_component package:
dart run wasm_wit_component:generate rust_wit_component_example/wit/types-example.wit lib/types_gen.dart
When using Flutter:
dependencies:
flutter:
sdk: flutter
wasm_run_flutter: 0.1.0
When using it in a pure Dart application:
dependencies:
wasm_run: 0.1.0
Fetch the dynamic libraries for your platform by running (or the function WasmRunLibrary.setUp
):
dart run wasm_run:setup
Or provide a custom dynamic library from the releases with WasmRunLibrary.set
.
You may explore the testAll
function in the main.dart file.
It contains multiple test using distinct APIs withing the browser or native.
A simple usage example is the following. It compiles a WASM module with an add
export and executes it:
/// WASM WAT source:
///
/// ```wat
/// (module
/// (func (export "add") (param $a i32) (param $b i32) (result i32)
/// local.get $a
/// local.get $b
/// i32.add
/// )
/// )
/// ```
const base64Binary =
'AGFzbQEAAAABBwFgAn9/AX8DAgEABwcBA2FkZAAACgkBBwAgACABagsAEARuYW1lAgkBAAIAAWEBAWI=';
final Uint8List binary = base64Decode(base64Binary);
final WasmModule module = await compileWasmModule(
binary,
config: const ModuleConfig(
wasmi: ModuleConfigWasmi(),
wasmtime: ModuleConfigWasmtime(),
),
);
final List<WasmModuleExport> exports = module.getExports();
assert(
exports.first.toString() ==
const WasmModuleExport('add', WasmExternalKind.function).toString(),
);
final List<WasmModuleImport> imports = module.getImports();
assert(imports.isEmpty);
// configure wasi
WasiConfig? wasiConfig;
final WasmInstanceBuilder builder = module.builder(wasiConfig: wasiConfig);
// create external
// builder.createTable
// builder.createGlobal
// builder.createMemory
// Add imports
// builder.addImport(moduleName, name, value);
final WasmInstance instance = await builder.build();
final WasmFunction add = instance.getFunction('add')!;
final List<ValueTy?> params = add.params;
assert(params.length == 2);
final WasmRuntimeFeatures runtime = await wasmRuntimeFeatures();
if (!runtime.isBrowser) {
// Types are not supported in browser
assert(params.every((t) => t == ValueTy.i32));
assert(add.results!.length == 1);
assert(add.results!.first == ValueTy.i32);
}
final List<Object?> result = add([1, 4]);
assert(result.length == 1);
assert(result.first == 5);
final resultInner = add.inner(-1, 8) as int;
assert(resultInner == 7);
At the moment, 64 bit integers are represented differently in the web browser and native platforms. Dart uses JavaScript's number
(double) for all numbers when compiled to JavaScript. Since they cannot properly represent the full range of 64 bit integers, WebAssembly uses JavaScript's BigInt (different from Dart's BigInt
) for the i64 Wasm type. You may choose to convert i64 values into integers using i64.toInt(I64 value)
(a no-op in native platforms) or to Dart's BigInt with i64.toBigInt(I64 value)
, where value is returned from or received as a parameter in a WasmFunction
, or accessed with WasmGlobal.get()
.
Unfortunately, JavaScript's BigInt is different from dart:core
's BigInt
and using it directly is not recommended, for example it throws when printed and there is some weird behavior with Dart function (we need to use js_util.callMethod(wasmFunction, 'apply', [null, args])
to call WasmFunction with I64
parameters). If you target web platforms you should always use it with the i64
utility functions for proper multi-platform support. Since native platforms represent i64 using Dart's int
class you can always cast the I64
values directly with value as int
, and functions such as i64.toInt
and i64.fromInt
simply return the value passed as argument (they are a no-op).
Other utility functions are provided, for the full API you may look at the source file:
i64.toInt
and i64.fromInt
i64.toBigInt
and i64.fromBigInt
i64.getInt64
, i64.getUint64
, i64.setInt64
and i64.setUint64
ByteData
to get or set the I64
values at the given byte offset
inside the buffer.WasmFileUris
You can load WasmModules
from Uris (using http GET or reading from a file) with the WasmFileUris
utility class. You may aldo provide the simdUri
and threadsSimdUri
fields to conditionally import different WasmModule
s based on the features supported by the current runtime (retrieved using wasmRuntimeFeatures
).
An usage example can be found in the wit component generator function, where the WASM module is loaded from the file system in lib/dart_wit_component.wasm
either reading it directly in native platforms or with a GET request for Dart web. As a fallback it will use the releases from the wasm_run repository. And it also received a loadModule
custom function if you want to override the behavior. This can be useful if you want to provide a different configuration or implementation, or you are loading it from Flutter assets or from a different HTTP endpoint.
WasmRunLibrary
The WasmRunLibrary
static utility class allows you to configure the external and dynamic libraries used by wasm_run
.
String version
getter to retrieve the package version ("0.1.0").set
static method.setUp
.
.dart_tool/wasm_run/<wasm_run_dart_dynamic_library>
or in the path provided by the WASM_RUN_DART_DYNAMIC_LIBRARY
environment variable. You can also run it with dart run wasm_run:setup
in a project that depends on wasm_run
.html.document.head
with script tags (import the libraries) required to use the wasm-feature-detect and bjorn3/browser_wasi_shim JavaScript libraries.In the following sections we will create a Dart package that uses a Wasm Wit component generated from a Rust implementation.
First we will create a Dart package and setup the Rust implementation within it. Then we will create a Wit package API
with the connected Rust implementation. This Rust package will be built to Wasm, either to a wasm32-wasi
or wasm32-unknown-unknown
target
depending on whether you require WASI (system) APIs. After that we will generate the wit Dart binding
using package:wasm_wit_component
's generate
CLI command. Finally, we will setup the Wasm module loading and implement some tests
for the wasm implementation within Dart.
dart create --template package your_package
cd your_package
cargo new --lib your_package_wasm
cd your_package_wasm
Edit the generated Cargo.toml
:
[lib]
crate-type = ['cdylib']
[dependencies]
wit-bindgen = { git = "https://github.com/bytecodealliance/wit-bindgen", version = "0.7.0", rev = "131746313de2f90d2688afbbc40c4a7ca309fe0d" }
image = "0.24.6"
package your-package-namespace:your-package
world your-package {
hello: func() -> string
}
Within your_package_wasm/src/lib.rs
:
cargo add cargo-wasi
cargo wasi build --release
cp target/wasm32-wasi/release/your_package_wasm.wasm ../lib/your_package_wasm.wasm
cd ..
dart pub add wasm_wit_component wasm_run
dart run wasm_wit_component:generate your_package_wasm/wit/your-package.wit lib/src/your_package_wit.gen.dart