Implement RFC 11: Redesigning Wasmtime's APIs (#2897)
Implement Wasmtime's new API as designed by RFC 11. This is quite a large commit which has had lots of discussion externally, so for more information it's best to read the RFC thread and the PR thread.
This commit is contained in:
@@ -17,7 +17,6 @@
|
||||
- [Linking Modules](./examples-rust-linking.md)
|
||||
- [Debugging](./examples-rust-debugging.md)
|
||||
- [Using Multi-Value](./examples-rust-multi-value.md)
|
||||
- [Multi-threading](./examples-rust-multithreading.md)
|
||||
- [Embedding in C](./examples-c-embed.md)
|
||||
- [Hello, World!](./examples-c-hello-world.md)
|
||||
- [Calculating the GCD](./examples-c-gcd.md)
|
||||
|
||||
@@ -2,6 +2,5 @@
|
||||
|
||||
This section is intended to showcase the Rust embedding API for Wasmtime. This
|
||||
is done through the [`wasmtime` crate](https://crates.io/crates/wasmtime). In
|
||||
addition to browsing the following examples you can also browse the [specific
|
||||
section on Rust embedding](./embed-rust.md) or the [full API
|
||||
addition to browsing the following examples you can also browse the [full API
|
||||
documentation](https://docs.rs/wasmtime).
|
||||
|
||||
@@ -6,7 +6,10 @@ repository to run the example locally.
|
||||
[code]: https://github.com/bytecodealliance/wasmtime/blob/main/examples/hello.rs
|
||||
|
||||
This example shows off how to instantiate a simple wasm module and interact with
|
||||
it.
|
||||
it. For more information about the types used here be sure to review the [core
|
||||
concepts of the `wasmtime`
|
||||
API](https://docs.rs/wasmtime/*/wasmtime/#core-concepts) as well as the general
|
||||
[API documentation](https://docs.rs/wasmtime).
|
||||
|
||||
## `hello.wat`
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@ repository to run the example locally.
|
||||
[code]: https://github.com/bytecodealliance/wasmtime/blob/main/examples/linking.rs
|
||||
|
||||
This example shows off how to compile and instantiate modules which link
|
||||
together.
|
||||
together. Be sure to read the API documentation for [`Linker`] as well.
|
||||
|
||||
[`Linker`]: https://docs.rs/wasmtime/0.26.0/wasmtime/struct.Linker.html
|
||||
|
||||
## `linking1.wat`
|
||||
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
# Multi-threading
|
||||
|
||||
When using Rust you're effectively immune from a whole class of threading issues
|
||||
such as data races due to the inherent checks in the compiler and traits like
|
||||
`Send` and `Sync`. The `wasmtime` API, like other safe Rust APIs, is 100% safe
|
||||
to use relative to threading if you never have any `unsafe` yourself. In
|
||||
addition to all of this, however, it's important to be aware of the limitations
|
||||
of `wasmtime` types and how this might affect your embedding use case.
|
||||
|
||||
## Types that are `Send` and `Sync`
|
||||
|
||||
Wasmtime has a number of types which implement both the `Send` and `Sync`
|
||||
traits:
|
||||
|
||||
* [`Config`](https://docs.wasmtime.dev/api/wasmtime/struct.Config.html)
|
||||
* [`Engine`](https://docs.wasmtime.dev/api/wasmtime/struct.Engine.html)
|
||||
* [`Module`](https://docs.wasmtime.dev/api/wasmtime/struct.Module.html)
|
||||
* [`Trap`](https://docs.wasmtime.dev/api/wasmtime/struct.Trap.html)
|
||||
* [`InterruptHandle`](https://docs.wasmtime.dev/api/wasmtime/struct.InterruptHandle.html)
|
||||
* Type-descriptions of items
|
||||
* [`ValType`](https://docs.wasmtime.dev/api/wasmtime/struct.ValType.html)
|
||||
* [`ExportType`](https://docs.wasmtime.dev/api/wasmtime/struct.ExportType.html)
|
||||
* [`ExternType`](https://docs.wasmtime.dev/api/wasmtime/struct.ExternType.html)
|
||||
* [`ImportType`](https://docs.wasmtime.dev/api/wasmtime/struct.ImportType.html)
|
||||
* [`FuncType`](https://docs.wasmtime.dev/api/wasmtime/struct.FuncType.html)
|
||||
* [`GlobalType`](https://docs.wasmtime.dev/api/wasmtime/struct.GlobalType.html)
|
||||
* [`MemoryType`](https://docs.wasmtime.dev/api/wasmtime/struct.MemoryType.html)
|
||||
* [`ModuleType`](https://docs.wasmtime.dev/api/wasmtime/struct.ModuleType.html)
|
||||
* [`TableType`](https://docs.wasmtime.dev/api/wasmtime/struct.TableType.html)
|
||||
* [`InstanceType`](https://docs.wasmtime.dev/api/wasmtime/struct.InstanceType.html)
|
||||
|
||||
These types, as the traits imply, are safe to send and share across threads.
|
||||
Note that the major types to call out here are `Module` and `Engine`. The
|
||||
`Engine` is important because it enables sharing compilation configuration for
|
||||
an entire application. Each `Engine` is intended to be long-lived for this
|
||||
reason.
|
||||
|
||||
Additionally `Module`, the compiled version of a WebAssembly module, is safe to
|
||||
send and share across threads. This notably means that you can compile a module
|
||||
once and then instantiate it on multiple threads simultaneously. There's no need
|
||||
to recompile a module on each thread.
|
||||
|
||||
## Types that are neither `Send` nor `Sync`
|
||||
|
||||
Wasmtime also has a number of types which are thread-"unsafe". These types do
|
||||
not have the `Send` or `Sync` traits implemented which means that you won't be
|
||||
able to send them across threads by default.
|
||||
|
||||
* [`Store`](https://docs.wasmtime.dev/api/wasmtime/struct.Store.html)
|
||||
* [`Linker`](https://docs.wasmtime.dev/api/wasmtime/struct.Linker.html)
|
||||
* [`Instance`](https://docs.wasmtime.dev/api/wasmtime/struct.Instance.html)
|
||||
* [`Extern`](https://docs.wasmtime.dev/api/wasmtime/struct.Extern.html)
|
||||
* [`Func`](https://docs.wasmtime.dev/api/wasmtime/struct.Func.html)
|
||||
* [`Global`](https://docs.wasmtime.dev/api/wasmtime/struct.Global.html)
|
||||
* [`Table`](https://docs.wasmtime.dev/api/wasmtime/struct.Table.html)
|
||||
* [`Memory`](https://docs.wasmtime.dev/api/wasmtime/struct.Memory.html)
|
||||
* [`Val`](https://docs.wasmtime.dev/api/wasmtime/struct.Val.html)
|
||||
* [`ExternRef`](https://docs.wasmtime.dev/api/wasmtime/struct.ExternRef.html)
|
||||
|
||||
These types are all considered as "connected to a store", and everything
|
||||
connected to a store is neither `Send` nor `Sync`. The Rust compiler will not
|
||||
allow you to have values of these types cross thread boundaries or get shared
|
||||
between multiple threads. Doing so would require some form of `unsafe` glue.
|
||||
|
||||
It's important to note that the WebAssembly specification itself fundamentally
|
||||
limits some of the concurrent possibilities here. For example it's not allowed
|
||||
to concurrently call `global.set` or `table.set` on the same global/table. This
|
||||
means that Wasmtime is designed to prevent at the very least concurrent usage of
|
||||
these primitives.
|
||||
|
||||
Apart from the WebAssembly specification, though, Wasmtime additionally has some
|
||||
fundamental design decision which results in these types not implementing either
|
||||
`Send` or `Sync`:
|
||||
|
||||
* All objects are independently-owned `'static` values that internally retain
|
||||
anything necessary to implement the API provided. This necessitates some form
|
||||
of reference counting, and also requires the usage of non-atomic reference
|
||||
counting. Once reference counting is used Rust only allows shared references
|
||||
(`&T`) to the internals, and due to the wasm restriction of disallowing
|
||||
concurrent usage non-atomic reference counting is used.
|
||||
|
||||
* Insertion of user-defined objects into `Store` does not require all objects to
|
||||
be either `Send` or `Sync`. For example `Func::wrap` will insert the
|
||||
host-defined function into the `Store`, but there are no extra trait bounds on
|
||||
this. Similar restrictions apply to `Store::set` as well.
|
||||
|
||||
* The implementation of `ExternRef` allows arbitrary `'static` types `T` to get
|
||||
wrapped up and is also implemented with non-atomic reference counting.
|
||||
|
||||
Overall the design decisions of Wasmtime itself leads all of these types to not
|
||||
implement either the `Send` or `Sync` traits.
|
||||
|
||||
## Multithreading without `Send`
|
||||
|
||||
Due to the lack of `Send` on types like `Store` and everything connected, it's
|
||||
not always as trivial to add multithreaded execution of WebAssembly to an
|
||||
embedding of Wasmtime as it is for other Rust code in general. The exact way
|
||||
that multithreading could work for you depends on your specific embedding, but
|
||||
some possibilities include:
|
||||
|
||||
* If your workload involves instantiating a singular wasm module on a separate
|
||||
thread, then it will need to live on that thread and communicate to other
|
||||
threads via threadsafe means (e.g. channels, locks/queues, etc).
|
||||
|
||||
* If you have something like a multithreaded web server, for example, then the
|
||||
WebAssembly executed for each request will need to live within the thread that
|
||||
the original `Store` was created on. This could be multithreaded, though, by
|
||||
having a pool of threads executing WebAssembly. Each request would have a
|
||||
scheduling decision of which pool to route to which would be up to the
|
||||
application. In situations such as this it's recommended to [enable fuel
|
||||
consumption](https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.consume_fuel)
|
||||
as well as [yielding when out of
|
||||
fuel](https://docs.wasmtime.dev/api/wasmtime/struct.Store.html#method.out_of_fuel_async_yield).
|
||||
This will ensure that no one request entirely hogs a thread executing
|
||||
WebAssembly and all requests scheduled onto that thread are able to execute.
|
||||
It's also worth pointing out that the threads executing WebAssembly may or may
|
||||
not be the same as the threads performing I/O for your server requests.
|
||||
|
||||
* If absolutely required, Wasmtime is engineered such that it is dynamically safe
|
||||
to move a `Store` as a whole to a separate thread. This option is not
|
||||
recommended due to its complexity, but it is one that Wasmtime tests in CI and
|
||||
considers supported. The principle here is that all objects connected to a
|
||||
`Store` are safe to move to a separate thread *if and only if*:
|
||||
|
||||
* All objects are moved all at once. For example you can't leave behind
|
||||
references to a `Func` or perhaps a `Store` in TLS.
|
||||
|
||||
* All host objects living inside of a store (e.g. those inserted via
|
||||
`Store::set` or `Func::wrap`) implement the `Send` trait.
|
||||
|
||||
If these requirements are met it is technically safe to move a store and its
|
||||
objects between threads. The reason that this strategy isn't recommended,
|
||||
however, is that you will receive no assistance from the Rust compiler in
|
||||
verifying that the transfer across threads is indeed actually safe. This will
|
||||
require auditing your embedding of Wasmtime itself to ensure it meets these
|
||||
requirements.
|
||||
|
||||
It's important to note that the requirements here also apply to the futures
|
||||
returned from `Func::call_async`. These futures are not `Send` due to them
|
||||
closing over `Store`-related values. In addition to the above requirements
|
||||
though to safely send across threads embedders must *also* ensure that any
|
||||
host futures returned from `Func::wrapN_async` are actually `Send` and safe to
|
||||
send across threads. Again, though, there is no compiler assistance in doing
|
||||
this.
|
||||
|
||||
Overall the recommended story for multithreading with Wasmtime is "don't move a
|
||||
`Store` between threads" and to architect your application around this
|
||||
assumption.
|
||||
@@ -5,7 +5,12 @@ repository to run the example locally.
|
||||
|
||||
[code]: https://github.com/bytecodealliance/wasmtime/blob/main/examples/wasi/main.rs
|
||||
|
||||
This example shows off how to instantiate a wasm module using WASI imports.
|
||||
This example shows how to use the [`wasmtime-wasi`] crate to define WASI
|
||||
functions within a [`Linker`] which can then be used to instantiate a
|
||||
WebAssembly module.
|
||||
|
||||
[`wasmtime-wasi`]: https://crates.io/crates/wasmtime-wasi
|
||||
[`Linker`]: https://docs.rs/wasmtime/*/wasmtime/struct.Linker.html
|
||||
|
||||
## Wasm Source code
|
||||
|
||||
@@ -13,9 +18,55 @@ This example shows off how to instantiate a wasm module using WASI imports.
|
||||
{{#include ../examples/wasi/wasm/wasi.rs}}
|
||||
```
|
||||
|
||||
|
||||
## `wasi.rs`
|
||||
|
||||
```rust,ignore
|
||||
{{#include ../examples/wasi/main.rs}}
|
||||
```
|
||||
|
||||
## WASI state with other custom host state
|
||||
|
||||
The [`add_to_linker`] takes a second argument which is a closure to access `&mut
|
||||
WasiCtx` from within the `T` stored in the `Store<T>` itself. In the above
|
||||
example this is trivial because the `T` in `Store<T>` is `WasiCtx` itself, but
|
||||
you can also store other state in `Store` like so:
|
||||
|
||||
[`add_to_linker`]: https://docs.rs/wasmtime-wasi/*/wasmtime_wasi/sync/fn.add_to_linker.html
|
||||
[`Store`]: https://docs.rs/wasmtime/0.26.0/wasmtime/struct.Store.html
|
||||
[`BorrowMut<WasiCtx>`]: https://doc.rust-lang.org/stable/std/borrow/trait.BorrowMut.html
|
||||
[`WasiCtx`]: https://docs.rs/wasmtime-wasi/*/wasmtime_wasi/struct.WasiCtx.html
|
||||
|
||||
```rust
|
||||
# extern crate wasmtime;
|
||||
# extern crate wasmtime_wasi;
|
||||
# extern crate anyhow;
|
||||
use anyhow::Result;
|
||||
use std::borrow::{Borrow, BorrowMut};
|
||||
use wasmtime::*;
|
||||
use wasmtime_wasi::{WasiCtx, sync::WasiCtxBuilder};
|
||||
|
||||
struct MyState {
|
||||
message: String,
|
||||
wasi: WasiCtx,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let engine = Engine::default();
|
||||
let mut linker = Linker::new(&engine);
|
||||
wasmtime_wasi::add_to_linker(&mut linker, |state: &mut MyState| &mut state.wasi)?;
|
||||
|
||||
let wasi = WasiCtxBuilder::new()
|
||||
.inherit_stdio()
|
||||
.inherit_args()?
|
||||
.build();
|
||||
let mut store = Store::new(&engine, MyState {
|
||||
message: format!("hello!"),
|
||||
wasi,
|
||||
});
|
||||
|
||||
// ...
|
||||
|
||||
# let _linker: Linker<MyState> = linker;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
@@ -55,10 +55,9 @@ use std::error::Error;
|
||||
use wasmtime::*;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// An engine stores and configures global compilation settings like
|
||||
// optimization level, enabled wasm features, etc.
|
||||
let engine = Engine::default();
|
||||
// A `Store` is a sort of "global object" in a sense, but for now it suffices
|
||||
// to say that it's generally passed to most constructors.
|
||||
let store = Store::new(&engine);
|
||||
|
||||
# if false {
|
||||
// We start off by creating a `Module` which represents a compiled form
|
||||
@@ -68,24 +67,30 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
# }
|
||||
# let module = Module::new(&engine, r#"(module (func (export "answer") (result i32) i32.const 42))"#)?;
|
||||
|
||||
// After we have a compiled `Module` we can then instantiate it, creating
|
||||
// A `Store` is what will own instances, functions, globals, etc. All wasm
|
||||
// items are stored within a `Store`, and it's what we'll always be using to
|
||||
// interact with the wasm world. Custom data can be stored in stores but for
|
||||
// now we just use `()`.
|
||||
let mut store = Store::new(&engine, ());
|
||||
|
||||
// With a compiled `Module` we can then instantiate it, creating
|
||||
// an `Instance` which we can actually poke at functions on.
|
||||
let instance = Instance::new(&store, &module, &[])?;
|
||||
let instance = Instance::new(&mut store, &module, &[])?;
|
||||
|
||||
// The `Instance` gives us access to various exported functions and items,
|
||||
// which we access here to pull out our `answer` exported function and
|
||||
// run it.
|
||||
let answer = instance.get_func("answer")
|
||||
let answer = instance.get_func(&mut store, "answer")
|
||||
.expect("`answer` was not an exported function");
|
||||
|
||||
// There's a few ways we can call the `answer` `Func` value. The easiest
|
||||
// is to statically assert its signature with `typed` (in this case
|
||||
// asserting it takes no arguments and returns one i32) and then call it.
|
||||
let answer = answer.typed::<(), i32>()?;
|
||||
let answer = answer.typed::<(), i32, _>(&store)?;
|
||||
|
||||
// And finally we can call our function! Note that the error propagation
|
||||
// with `?` is done to handle the case where the wasm function traps.
|
||||
let result = answer.call(())?;
|
||||
let result = answer.call(&mut store, ())?;
|
||||
println!("Answer: {:?}", result);
|
||||
Ok(())
|
||||
}
|
||||
@@ -142,30 +147,50 @@ looks like this:
|
||||
use std::error::Error;
|
||||
use wasmtime::*;
|
||||
|
||||
struct Log {
|
||||
integers_logged: Vec<u32>,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let engine = Engine::default();
|
||||
let store = Store::new(&engine);
|
||||
# if false {
|
||||
let module = Module::from_file(&engine, "hello.wat")?;
|
||||
# }
|
||||
# let module = Module::new(&engine, r#"(module (import "" "log" (func $log (param i32))) (import "" "double" (func $double (param i32) (result i32))) (func (export "run") i32.const 0 call $log i32.const 1 call $log i32.const 2 call $double call $log))"#)?;
|
||||
|
||||
// First we can create our `log` function, which will simply print out the
|
||||
// parameter it receives.
|
||||
let log = Func::wrap(&store, |param: i32| {
|
||||
// For host-provided functions it's recommended to use a `Linker` which does
|
||||
// name-based resolution of functions.
|
||||
let mut linker = Linker::new(&engine);
|
||||
|
||||
// First we create our simple "double" function which will only multiply its
|
||||
// input by two and return it.
|
||||
linker.func_wrap("", "double", |param: i32| param * 2);
|
||||
|
||||
// Next we define a `log` function. Note that we're using a
|
||||
// Wasmtime-provided `Caller` argument to access the state on the `Store`,
|
||||
// which allows us to record the logged information.
|
||||
linker.func_wrap("", "log", |mut caller: Caller<'_, Log>, param: u32| {
|
||||
println!("log: {}", param);
|
||||
caller.data_mut().integers_logged.push(param);
|
||||
});
|
||||
|
||||
// Next we can create our double function which doubles the input it receives.
|
||||
let double = Func::wrap(&store, |param: i32| param * 2);
|
||||
// As above, instantiation always happens within a `Store`. This means to
|
||||
// actually instantiate with our `Linker` we'll need to create a store. Note
|
||||
// that we're also initializing the store with our custom data here too.
|
||||
//
|
||||
// Afterwards we use the `linker` to create the instance.
|
||||
let data = Log { integers_logged: Vec::new() };
|
||||
let mut store = Store::new(&engine, data);
|
||||
let instance = linker.instantiate(&mut store, &module)?;
|
||||
|
||||
// When instantiating the module we now need to provide the imports to the
|
||||
// instantiation process. This is the second slice argument, where each
|
||||
// entry in the slice must line up with the imports in the module.
|
||||
let instance = Instance::new(&store, &module, &[log.into(), double.into()])?;
|
||||
// Like before, we can get the run function and execute it.
|
||||
let run = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
|
||||
run.call(&mut store, ())?;
|
||||
|
||||
let run = instance.get_typed_func::<(), ()>("run")?;
|
||||
Ok(run.call(())?)
|
||||
// We can also inspect what integers were logged:
|
||||
println!("logged integers: {:?}", store.data().integers_logged);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -37,8 +37,7 @@ You can also see how this works in the Rust API like so:
|
||||
use wasmtime::*;
|
||||
|
||||
# fn main() -> anyhow::Result<()> {
|
||||
let engine = Engine::default();
|
||||
let store = Store::new(&engine);
|
||||
let mut store = Store::<()>::default();
|
||||
let wat = r#"
|
||||
(module
|
||||
(func (export "add") (param i32 i32) (result i32)
|
||||
@@ -46,10 +45,10 @@ let wat = r#"
|
||||
local.get 1
|
||||
i32.add))
|
||||
"#;
|
||||
let module = Module::new(&engine, wat)?;
|
||||
let instance = Instance::new(&store, &module, &[])?;
|
||||
let add = instance.get_typed_func::<(i32, i32), i32>("add")?;
|
||||
println!("1 + 2 = {}", add.call((1, 2))?);
|
||||
let module = Module::new(store.engine(), wat)?;
|
||||
let instance = Instance::new(&mut store, &module, &[])?;
|
||||
let add = instance.get_typed_func::<(i32, i32), i32, _>(&mut store, "add")?;
|
||||
println!("1 + 2 = {}", add.call(&mut store, (1, 2))?);
|
||||
# Ok(())
|
||||
# }
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user