Prior to this PR a major feature of calling component exports (#4039) was the usage of the `Value<T>` type. This type represents a value stored in wasm linear memory (the type `T` stored there). This implementation had a number of drawbacks though: * When returning a value it's ABI-specific whether you use `T` or `Value<T>` as a return value. If `T` is represented with one wasm primitive then you have to return `T`, otherwise the return value must be `Value<T>`. This is somewhat non-obvious and leaks ABI-details into the API which is unfortunate. * The `T` in `Value<T>` was somewhat non-obvious. For example a wasm-owned string was `Value<String>`. Using `Value<&str>` didn't work. * Working with `Value<T>` was unergonomic in the sense that you had to first "pair" it with a `&Store<U>` to get a `Cursor<T>` and then you could start reading the value. * Custom structs and enums, while not implemented yet, were planned to be quite wonky where when you had `Cursor<MyStruct>` then you would have to import a `CursorMyStructExt` trait generated by a proc-macro (think a `#[derive]` on the definition of `MyStruct`) which would enable field accessors, returning cursors of all the fields. * In general there was no "generic way" to load a `T` from memory. Other operations like lift/lower/store all had methods in the `ComponentValue` trait but load had no equivalent. None of these drawbacks were deal-breakers per-se. When I started to implement imported functions, though, the `Value<T>` type no longer worked. The major difference between imports and exports is that when receiving values from wasm an export returns at most one wasm primitive where an import can yield (through arguments) up to 16 wasm primitives. This means that if an export returned a string it would always be `Value<String>` but if an import took a string as an argument there was actually no way to represent this with `Value<String>` since the value wasn't actually stored in memory but rather the pointer/length pair is received as arguments. Overall this meant that `Value<T>` couldn't be used for arguments-to-imports, which means that altogether something new would be required. This PR completely removes the `Value<T>` and `Cursor<T>` type in favor of a different implementation. The inspiration from this comes from the fact that all primitives can be both lifted and lowered into wasm while it's just some times which can only go one direction. For example `String` can be lowered into wasm but can't be lifted from wasm. Instead some sort of "view" into wasm needs to be created during lifting. One of the realizations from #4039 was that we could leverage run-time-type-checking to reject static constructions that don't make sense. For example if an embedder asserts that a wasm function returns a Rust `String` we can reject that at typechecking time because it's impossible for a wasm module to ever do that. The new system of imports/exports in this PR now looks like: * Type-checking takes into accont an `Op` operation which indicates whether we'll be lifting or lowering the type. This means that we can allow the lowering operation for `String` but disallow the lifting operation. While we can't statically rule out an embedder saying that a component returns a `String` we can now reject it at runtime and disallow it from being called. * The `ComponentValue` trait now sports a new `load` function. This function will load and instance of `Self` from the byte-array provided. This is implemented for all types but only ever actually executed when the `lift` operation is allowed during type-checking. * The `Lift` associated type is removed since it's now expected that the lift operation returns `Self`. * The `ComponentReturn` trait is now no longer necessary and is removed. Instead returns are bounded by `ComponentValue`. During type-checking it's required that the return value can be lifted, disallowing, for example, returning a `String` or `&str`. * With `Value` gone there's no need to specify the ABI details of the return value, or whether it's communicated through memory or not. This means that handling return values through memory is transparently handled by Wasmtime. * Validation is in a sense more eagerly performed now. Whenever a value `T` is loaded the entire immediate structure of `T` is loaded and validated. Note that recursive through memory validation still does not happen, so the contents of lists or strings aren't validated, it's just validated that the pointers are in-bounds. Overall this felt like a much clearer system to work with and should be much easier to integrate with imported functions as well. The new `WasmStr` and `WasmList<T>` types can be used in import arguments and lifted from the immediate arguments provided rather than forcing them to always be stored in memory.
About
This crate is the Rust embedding API for the Wasmtime project: a cross-platform engine for running WebAssembly programs. Notable features of Wasmtime are:
-
Fast. Wasmtime is built on the optimizing Cranelift code generator to quickly generate high-quality machine code either at runtime or ahead-of-time. Wasmtime's runtime is also optimized for cases such as efficient instantiation, low-overhead transitions between the embedder and wasm, and scalability of concurrent instances.
-
Secure. Wasmtime's development is strongly focused on the correctness of its implementation with 24/7 fuzzing donated by Google's OSS Fuzz, leveraging Rust's API and runtime safety guarantees, careful design of features and APIs through an RFC process, a security policy in place for when things go wrong, and a release policy for patching older versions as well. We follow best practices for defense-in-depth and known protections and mitigations for issues like Spectre. Finally, we're working to push the state-of-the-art by collaborating with academic researchers to formally verify critical parts of Wasmtime and Cranelift.
-
Configurable. Wastime supports a rich set of APIs and build time configuration to provide many options such as further means of restricting WebAssembly beyond its basic guarantees such as its CPU and Memory consumption. Wasmtime also runs in tiny environments all the way up to massive servers with many concurrent instances.
-
WASI. Wasmtime supports a rich set of APIs for interacting with the host environment through the WASI standard.
-
Standards Compliant. Wasmtime passes the official WebAssembly test suite, implements the official C API of wasm, and implements future proposals to WebAssembly as well. Wasmtime developers are intimately engaged with the WebAssembly standards process all along the way too.
Example
An example of using the Wasmtime embedding API for running a small WebAssembly module might look like:
use anyhow::Result;
use wasmtime::*;
fn main() -> Result<()> {
// Modules can be compiled through either the text or binary format
let engine = Engine::default();
let wat = r#"
(module
(import "host" "hello" (func $host_hello (param i32)))
(func (export "hello")
i32.const 3
call $host_hello)
)
"#;
let module = Module::new(&engine, wat)?;
// Create a `Linker` which will be later used to instantiate this module.
// Host functionality is defined by name within the `Linker`.
let mut linker = Linker::new(&engine);
linker.func_wrap("host", "hello", |caller: Caller<'_, u32>, param: i32| {
println!("Got {} from WebAssembly", param);
println!("my host state is: {}", caller.data());
})?;
// All wasm objects operate within the context of a "store". Each
// `Store` has a type parameter to store host-specific data, which in
// this case we're using `4` for.
let mut store = Store::new(&engine, 4);
let instance = linker.instantiate(&mut store, &module)?;
let hello = instance.get_typed_func::<(), (), _>(&mut store, "hello")?;
// And finally we can call the wasm!
hello.call(&mut store, ())?;
Ok(())
}
More examples and information can be found in the wasmtime crate's online
documentation as well.
Documentation
📚 Read the Wasmtime guide here! 📚
The wasmtime guide is the best starting point to learn about what Wasmtime can do for you or help answer your questions about Wasmtime. If you're curious in contributing to Wasmtime, it can also help you do that!