6 releases

0.2.0 Feb 27, 2024
0.1.4 Oct 27, 2023
0.1.3 Sep 25, 2023
0.1.2 Apr 27, 2023
0.1.1 Mar 28, 2023

#20 in #wasm-interpreter

Download history 125/week @ 2024-02-22 37/week @ 2024-02-29 8/week @ 2024-03-07 7/week @ 2024-03-14 17/week @ 2024-03-28 9/week @ 2024-04-04 67/week @ 2024-04-18

93 downloads per month
Used in wasefire-scheduler

Apache-2.0

160KB
4K SLoC

WebAssembly interpreter with customizable trade-off between footprint and performance.

The main concepts of this crate are:

  • A Store contains instantiated modules and permits execution. Note that execution within the same store must follow a stack behavior. A function "bar" may be called while a function "foo" is running: "bar" will temporarily interrupt "foo" until "bar" returns at which point "foo" would resume. This is to avoid corrupting the stack within the same instance. Once the WebAssembly threads proposal lands, this restriction may be removed.

  • A Module represents a valid module. Only valid modules may be instantiated. A module is just a byte slice holding a WebAssembly module in binary format.

  • A RunResult represents the result of a (possibly partial) execution. When invoking an exported function from an instance in a store, or when resuming the execution after a call to the host, a RunResult is returned providing either the next call to the host or whether execution of the most recently invoked function terminated along with its results.

Examples

For a concrete "hello" example, see examples/hello.rs which walks through most important steps in using this crate. Otherwise, here are some short excerpts:

Creating a store can simply use the Default trait:

let mut store = Store::default();

Linking a host function in a store is done with [Store::link_func()]:

store.link_func("env", "add", 2, 1)?;

Instantiating a valid module in a store is done with [Store::instantiate()]:

let inst = store.instantiate(module, memory)?;

Invoking a function exported by an instance is done with [Store::invoke()]:

let mut result = store.invoke(inst, "mul", vec![Val::I32(13), Val::I32(29)])?;

Processing a call to the host is done with the Call in RunResult::Host:

loop {
    let mut call = match result {
        RunResult::Done(results) => return Ok(results),
        RunResult::Host(call) => call,
    };
    let results = process(&mut call)?;
    result = call.resume(&results)?;
}

Atomic support

This crate uses atomic operations and relies on the portable-atomic crate to support architectures without atomic operations. Depending on your architecture, you may need one of the following:

  • Enable the portable-atomic/critical-section feature (possibly adding a direct dependency on portable-atomic).

  • Set the --cfg=portable_atomic_unsafe_assume_single_core rustc flag.

You can find more information in the portable-atomic documentation.

Dependencies

~1.2–1.8MB
~36K SLoC