6 releases
0.19.0 | Jul 23, 2020 |
---|---|
0.16.0-threadsafe.4 | May 19, 2020 |
0.13.0-threadsafe.1 | Mar 12, 2020 |
#633 in WebAssembly
164 downloads per month
2MB
33K
SLoC
Wasmtime Embedding API
The wasmtime
crate is an embedding API of the wasmtime
WebAssembly runtime.
This is intended to be used in Rust projects and provides a high-level API of
working with WebAssembly modules.
If you're interested in embedding wasmtime
in other languages, you may wish to
take a look a the C embedding API instead!
lib.rs
:
Wasmtime's embedding API
This crate contains an API used to interact with WebAssembly modules. For example you can compile modules, instantiate them, call them, etc. As an embedder of WebAssembly you can also provide WebAssembly modules functionality from the host by creating host-defined functions, memories, globals, etc, which can do things that WebAssembly cannot (such as print to the screen).
The wasmtime
crate draws inspiration from a number of sources, including
the JS WebAssembly
API as well as the
proposed C API. As with all
other Rust code you're guaranteed that programs will be safe (not have
undefined behavior or segfault) so long as you don't use unsafe
in your
own program. With wasmtime
you can easily and conveniently embed a
WebAssembly runtime with confidence that the WebAssembly is safely
sandboxed.
An example of using Wasmtime looks like:
use anyhow::Result;
use wasmtime::*;
fn main() -> Result<()> {
// All wasm objects operate within the context of a "store"
let store = Store::default();
// Modules can be compiled through either the text or binary format
let wat = r#"
(module
(import "" "" (func $host_hello (param i32)))
(func (export "hello")
i32.const 3
call $host_hello)
)
"#;
let module = Module::new(store.engine(), wat)?;
// Host functions can be defined which take/return wasm values and
// execute arbitrary code on the host.
let host_hello = Func::wrap(&store, |param: i32| {
println!("Got {} from WebAssembly", param);
});
// Instantiation of a module requires specifying its imports and then
// afterwards we can fetch exports by name, as well as asserting the
// type signature of the function with `get0`.
let instance = Instance::new(&store, &module, &[host_hello.into()])?;
let hello = instance
.get_func("hello")
.ok_or(anyhow::format_err!("failed to find `hello` function export"))?
.get0::<()>()?;
// And finally we can call the wasm as if it were a Rust function!
hello()?;
Ok(())
}
Core Concepts
There are a number of core types and concepts that are important to be aware
of when using the wasmtime
crate:
-
Reference counting - almost all objects in this API are reference counted. Most of the time when and object is
clone
d you're just bumping a reference count. For example when you clone anInstance
that is a cheap operation, it doesn't create an entirely new instance. -
Store
- all WebAssembly object and host values will be "connected" to a store. AStore
is not threadsafe which means that itself and all objects connected to it are pinned to a single thread (this happens automatically through a lack of theSend
andSync
traits). Similarlywasmtime
does not have a garbage collector so anything created within aStore
will not be deallocated until all references have gone away. See theStore
documentation for more information. -
Module
- a compiled WebAssembly module. This structure represents in-memory JIT code which is ready to execute after being instantiated. It's often important to cache instances of aModule
because creation (compilation) can be expensive. Note thatModule
is safe to share across threads. -
Instance
- an instantiated WebAssembly module. An instance is where you can actually acquire aFunc
from, for example, to call. EachInstance
, like all otherStore
-connected objects, cannot be sent across threads.
There are other important types within the wasmtime
crate but it's crucial
to be familiar with the above types! Be sure to browse the API documentation
to get a feeling for what other functionality is offered by this crate.
Example Architecture
To better understand how Wasmtime types interact with each other let's walk
through, at a high-level, an example of how you might use WebAssembly. In
our use case let's say we have a web server where we'd like to run some
custom WebAssembly on each request. To ensure requests are isolated from
each other, though, we'll be creating a new Instance
for each request.
When the server starts, we'll start off by creating an Engine
(and maybe
tweaking Config
settings if necessary). This Engine
will be the only
engine for the lifetime of the server itself.
Next, we can compile our WebAssembly. You'd create a Module
through the
Module::new
API. This will generate JIT code and perform expensive
compilation tasks up-front.
After that setup, the server starts up as usual and is ready to receive
requests. Upon receiving a request you'd then create a Store
with
Store::new
referring to the original Engine
. Using your Module
from before you'd then call Instance::new
to instantiate our module for
the request. Both of these operations are designed to be as cheap as
possible.
With an Instance
you can then invoke various exports and interact with
the WebAssembly module. Once the request is finished the Store
,
Instance
, and all other items loaded are dropped and everything will be
deallocated. Note that it's crucial to create a Store
-per-request to
ensure that memory usage doesn't balloon accidentally by keeping a Store
alive indefinitely.
Advanced Linking
Often WebAssembly modules are not entirely self-isolated. They might refer
to quite a few pieces of host functionality, WASI, or maybe even a number of
other wasm modules. To help juggling all this together this crate provides a
Linker
type which serves as an abstraction to assist in instantiating a
module. The Linker
type also transparently handles Commands and Reactors
as defined by WASI.
WASI
The wasmtime
crate does not natively provide support for WASI, but you can
use the wasmtime-wasi
crate for that purpose. With wasmtime-wasi
you can
create a "wasi instance" and then add all of its items into a Linker
,
which can then be used to instantiate a Module
that uses WASI.
Examples
In addition to the examples below be sure to check out the online embedding documentation as well as the online list of examples
An example of using WASI looks like:
use wasmtime_wasi::{Wasi, WasiCtx};
let store = Store::default();
let mut linker = Linker::new(&store);
// Create an instance of `Wasi` which contains a `WasiCtx`. Note that
// `WasiCtx` provides a number of ways to configure what the target program
// will have access to.
let wasi = Wasi::new(&store, WasiCtx::new(std::env::args())?);
wasi.add_to_linker(&mut linker)?;
// Instantiate our module with the imports we've created, and run it.
let module = Module::from_file(store.engine(), "foo.wasm")?;
let instance = linker.instantiate(&module)?;
// ...
An example of reading a string from a wasm module:
use std::str;
let store = Store::default();
let log_str = Func::wrap(&store, |caller: Caller<'_>, ptr: i32, len: i32| {
let mem = match caller.get_export("memory") {
Some(Extern::Memory(mem)) => mem,
_ => return Err(Trap::new("failed to find host memory")),
};
// We're reading raw wasm memory here so we need `unsafe`. Note
// though that this should be safe because we don't reenter wasm
// while we're reading wasm memory, nor should we clash with
// any other memory accessors (assuming they're well-behaved
// too).
unsafe {
let data = mem.data_unchecked()
.get(ptr as u32 as usize..)
.and_then(|arr| arr.get(..len as u32 as usize));
let string = match data {
Some(data) => match str::from_utf8(data) {
Ok(s) => s,
Err(_) => return Err(Trap::new("invalid utf-8")),
},
None => return Err(Trap::new("pointer/length out of bounds")),
};
assert_eq!(string, "Hello, world!");
println!("{}", string);
}
Ok(())
});
let module = Module::new(
store.engine(),
r#"
(module
(import "" "" (func $log_str (param i32 i32)))
(func (export "foo")
i32.const 4 ;; ptr
i32.const 13 ;; len
call $log_str)
(memory (export "memory") 1)
(data (i32.const 4) "Hello, world!"))
"#,
)?;
let instance = Instance::new(&store, &module, &[log_str.into()])?;
let foo = instance.get_func("foo").unwrap().get0::<()>()?;
foo()?;
Dependencies
~22–35MB
~659K SLoC