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:
Alex Crichton
2021-06-03 09:10:53 -05:00
committed by GitHub
parent a5a28b1c5b
commit 7a1b7cdf92
233 changed files with 13349 additions and 11997 deletions

View File

@@ -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)

View File

@@ -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).

View File

@@ -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`

View File

@@ -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`

View File

@@ -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.

View File

@@ -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(())
}
```

View File

@@ -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(())
}
```

View File

@@ -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(())
# }
```