Introduction

Wasmtime is a Bytecode Alliance project that is a standalone wasm-only optimizing runtime for WebAssembly and WASI. It runs WebAssembly code outside of the Web, and can be used both as a command-line utility or as a library embedded in a larger application.

Wasmtime strives to be a highly configurable and embeddable runtime to run on any scale of application. Many features are still under development so if you have a question don't hesitate to file an issue.

This guide is intended to serve a number of purposes and within you'll find:

... and more! The source for this guide lives on GitHub and contributions are welcome!

Tutorial

This tutorial walks through creating a simple Hello World WebAssembly program and then running it.

Creating hello-world.wasm

There are a number of ways to create .wasm files but for the purposes of this tutorial, we'll be using the Rust toolchain. You can find more information on creating .wasm files from other languages in the Writing WebAssembly section.

To build WebAssembly binaries with Rust, you'll need the standard Rust toolchain.

Follow these instructions to install rustc, rustup and cargo

Next, you should add WebAssembly as a build target for cargo like so:

$ rustup target add wasm32-wasi

Finally, create a new Rust project called 'hello-world'. You can do this by running:

$ cargo new hello-world

After that, the hello-world folder should look like this.

hello-world/
├── Cargo.lock
├── Cargo.toml
└── src
   └── main.rs

And the main.rs file inside the src folder should contain the following rust code.

fn main() {
    println!("Hello, world!");
}

Now, we can tell cargo to build a WebAssembly file:

$ cargo build --target wasm32-wasi

Now, in the target folder, there's a hello-world.wasm file. You can find it here:

hello-world/
├── Cargo.lock
├── Cargo.toml
├── src
└── target
   └── ...
   └── wasm32-wasi
      └── debug
         └── ...
         └── hello-world.wasm

Running hello-world.wasm with Wasmtime

Installing Wasmtime

The Wasmtime CLI can be installed on Linux and macOS with a small install script:

$ curl https://wasmtime.dev/install.sh -sSf | bash

You can find more information about installing the Wasmtime CLI in the CLI Installation section

Running hello-world.wasm

There are a number of ways to run a .wasm file with Wasmtime. In this tutorial, we'll be using the CLI, Wasmtime can also be embedded in your applications. More information on this can be found in the Embedding Wasmtime section.

If you've built the hello-world.wasm file (the instructions for doing so are in the previous section), you can run it with Wasmtime from the command line like so:

$ wasmtime target/wasm32-wasi/debug/hello-world.wasm

Examples

The examples contained in this section explain how to use Wasmtime in several common scenarios.

Markdown Parser

The following steps describe an implementation of a WASI markdown parser, in Rust, using pulldown-cmark.

First, we will generate a new executable with cargo:

cargo new --bin rust_wasi_markdown_parser
cd rust_wasi_markdown_parser

Then, we will open the src/main.rs and enter the following contents. Please see the comments to understand what our program will be doing.

src/main.rs

// Import our CLI parsing libraries (And PathBuf for reading paths)
extern crate structopt;

use structopt::StructOpt;
use std::path::PathBuf;

// Import our markdown parser library, crate
extern crate pulldown_cmark;

use pulldown_cmark::{html, Parser};

// Import from the standard library, to allow reading from the file system
use std::fs;

// Define our CLI options using structopt
#[derive(StructOpt)]
#[structopt(name = "rust_wasi_markdown_parser", about = "Markdown to HTML renderer CLI, written with Rust & WASI")]
pub struct Options {
    /// The markdown file to render
    #[structopt(parse(from_os_str))]
    filename: PathBuf,
}

// Our entrypoint into our WASI module
fn main() {

    // Get the passed CLI options
    let options = Options::from_args();

    // Read the markdown file into a string
    let contents = fs::read_to_string(options.filename)
        .expect("Something went wrong reading the file");

    // Run our parsing function to get back an HTML string
    let result = render_markdown(contents);

    // Print out the resulting HTML to standard out
    println!("{}", result);
}

pub fn render_markdown(markdown: String) -> String {
    let mut html_buf = String::new();
    let parser = Parser::new(&markdown[..]);
    html::push_html(&mut html_buf, parser);
    html_buf
}

Next, we will want to add WASI as a target that we can compile to. We will ask the rustup tool to install support for WASI. Then, we will compile our program to WASI. To do this we will run:

rustup target add wasm32-wasi
cargo build --target wasm32-wasi

Our wasm file should be compiled to target/wasm32-wasi/debug/rust_wasi_markdown_parser.wasm. It is worth noting that even though the WASI APIs are not being used directly, when we compile our program to target WASI, the rust APIs and standard library will be using these WASI APIs under the hood for us! Now that we have our program compiled to target WASI, let's run our program!

To do this, we can use the Wasmtime CLI. However, there is one thing to note about Wasmtime, WASI, and the capability based security model. We need to give our program explicit access to read files on our device. Wasm modules that implement WASI will not have this capability unless we give them the capability.

To grant the capability to read in a directory using the Wasmtime CLI, we need to use the --dir flag. --dir will instruct wasmtime to make the passed directory available to access files from. (You can also --mapdir GUEST_DIRECTORY::HOST_DIRECTORY to make it available under a different path inside the content.) For example:

wasmtime --dir . my-wasi-program.wasm

For this example, we will be passing a markdown file to our program called: example_markdown.md, that will exist in whatever our current directory (./) is. Our markdown file, example_markdown.md, will contain:

# Hello!

I am example markdown for this demo!

So, to run our compiled WASI program, we will run:

wasmtime --dir . target/wasm32-wasi/debug/rust_wasi_markdown_parser.wasm -- ./example_markdown.md

Which should look like the following:

<h1>Hello!</h1>
<p>I am example markdown for this demo!</p>

Hooray! We were able to write a Wasm Module, that uses WASI to read a markdown file, parse the markdown, and write the output to stdout! Continue reading to see more examples of using Wasmtime to execute Wasm Modules, from the CLI or even embedded in your application!

Debugging WebAssembly

The following steps describe a common way to debug a WebAssembly module in Wasmtime:

  1. Compile your WebAssembly with debug info enabled, usually -g; for example:

    clang foo.c -g -o foo.wasm
    
  2. Run Wasmtime with the debug info enabled; this is -g from the CLI and Config::debug_info(true) in an embedding (e.g. see debugging in a Rust embedding)

  3. Use a supported debugger:

    lldb -- wasmtime run -g foo.wasm
    
    gdb --args wasmtime run -g foo.wasm
    

If you run into trouble, the following discussions might help:

  • On MacOS with LLDB you may need to run: settings set plugin.jit-loader.gdb.enable on (#1953)
  • With LLDB, call __vmctx.set() to set the current context before calling any dereference operators (#1482):
    (lldb) p __vmctx->set()
    (lldb) p *foo
    
  • The address of the start of instance memory can be found in __vmctx->memory

Profiling WebAssembly

One of WebAssembly's major goals is to be quite close to native code in terms of performance, so typically when executing Wasm you'll be quite interested in how well your Wasm module is performing! From time to time you might want to dive a bit deeper into the performance of your Wasm, and this is where profiling comes into the picture.

Profiling support in Wasmtime is still under development, but if you're using either perf or VTune the examples in these sections are targeted at helping you get some information about the performance of your Wasm modules.

Using perf on Linux

One profiler supported by Wasmtime is the perf profiler for Linux. This is an extremely powerful profiler with lots of documentation on the web, but for the rest of this section we'll assume you're running on Linux and already have perf installed.

Profiling support with perf uses the "jitdump" support in the perf CLI. This requires runtime support from Wasmtime itself, so you will need to manually change a few things to enable profiling support in your application. First you'll want to make sure that Wasmtime is compiled with the jitdump Cargo feature (which is enabled by default). Otherwise enabling runtime support depends on how you're using Wasmtime:

  • Rust API - you'll want to call the [Config::profiler] method with ProfilingStrategy::JitDump to enable profiling of your wasm modules.

  • C API - you'll want to call the wasmtime_config_profiler_set API with a WASMTIME_PROFILING_STRATEGY_JITDUMP value.

  • Command Line - you'll want to pass the --jitdump flag on the command line.

Once jitdump support is enabled, you'll use perf record like usual to record your application's performance. You'll need to also be sure to pass the --clockid mono or -k mono flag to perf record.

For example if you're using the CLI, you'll execute:

$ perf record -k mono wasmtime --jitdump foo.wasm

This will create a perf.data file as per usual, but it will also create a jit-XXXX.dump file. This extra *.dump file is the jitdump file which is specified by perf and Wasmtime generates at runtime.

The next thing you need to do is to merge the *.dump file into the perf.data file, which you can do with the perf inject command:

$ perf inject --jit --input perf.data --output perf.jit.data

This will read perf.data, automatically pick up the *.dump file that's correct, and then create perf.jit.data which merges all the JIT information together. This should also create a lot of jitted-XXXX-N.so files in the current directory which are ELF images for all the JIT functions that were created by Wasmtime.

After that you can explore the perf.jit.data profile as you usually would, for example with:

$ perf report --input perf.jit.data

You should be able to annotate wasm functions and see their raw assembly. You should also see entries for wasm functions show up as one function and the name of each function matches the debug name section in the wasm file.

Note that support for jitdump is still relatively new in Wasmtime, so if you have any problems, please don't hesitate to file an issue!

perf and DWARF information

If the jitdump profile doesn't give you enough information by default, you can also enable dwarf debug information to be generated for JIT code which should give the perf profiler more information about what's being profiled. This can include information like more desriptive function names, filenames, and line numbers.

Enabling dwarf debug information for JIT code depends on how you're using Wasmtime:

  • Rust API - you'll want to call the Config::debug_info method.

  • C API - you'll want to call the wasmtime_config_debug_info_set API.

  • Command Line - you'll want to pass the -g flag on the command line.

You shouldn't need to do anything else to get this information into perf. The perf collection data should automatically pick up all this dwarf debug information.

perf example

Let's run through a quick example with perf to get the feel for things. First let's take a look at some wasm:

fn main() {
    let n = 42;
    println!("fib({}) = {}", n, fib(n));
}

fn fib(n: u32) -> u32 {
    if n <= 2 {
        1
    } else {
        fib(n - 1) + fib(n - 2)
    }
}

To collect perf information for this wasm module we'll execute:

$ rustc --target wasm32-wasi fib.rs -O
$ perf record -k mono wasmtime --jitdump fib.wasm
fib(42) = 267914296
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.147 MB perf.data (3435 samples) ]
$ perf inject --jit --input perf.data --output perf.jit.data

And we should have all out information now! We can execute perf report for example to see that 99% of our runtime (as expected) is spent in our fib function. Note that the symbol has been demangled to fib::fib which is what the Rust symbol is:

$ perf report --input perf.jit.data

perf report output

Alternatively we could also use perf annotate to take a look at the disassembly of the fib function, seeing what the JIT generated:

$ perf annotate --input perf.jit.data

perf annotate output

Using VTune

VTune is a popular performance profiling tool that targets both 32-bit and 64-bit x86 architectures. The tool collects profiling data during runtime and then, either through the command line or GUI, provides a variety of options for viewing and analyzing that data. VTune Profiler is available in both commerical and free options. The free, downloadable version is available here and is backed by a community forum for support. This version is appropriate for detailed analysis of your Wasm program.

VTune support in Wasmtime is provided through the JIT profiling APIs from the ittapi library. This library provides code generators (or the runtimes that use them) a way to report JIT activities. The APIs are implemented in a static library (see ittapi source) which Wasmtime links to when VTune support is specified through the vtune Cargo feature flag; this feature is not enabled by default. When the VTune collector is run, the ittapi library collects Wasmtime's reported JIT activities. This connection to ittapi is provided by the ittapi-rs crate.

For more information on VTune and the analysis tools it provides see its documentation.

Turn on VTune support

For JIT profiling with VTune, Wasmtime currently builds with the vtune feature enabled by default. This ensures the compiled binary understands how to inform the ittapi library of JIT events. But it must still be enabled at runtime--enable runtime support based on how you use Wasmtime:

  • Rust API - call the [Config::profiler] method with ProfilingStrategy::VTune to enable profiling of your wasm modules.

  • C API - call the wasmtime_config_profiler_set API with a WASMTIME_PROFILING_STRATEGY_VTUNE value.

  • Command Line - pass the --vtune flag on the command line.

Profiling Wasmtime itself

Note that VTune is capable of profiling a single process or all system processes. Like perf, VTune is capable of profiling the Wasmtime runtime itself without any added support. However, the ittapi APIs also provide an interface for marking the start and stop of code regions for easy isolation in the VTune Profiler. Support for these APIs is expected to be added in the future.

Example: Getting Started

With VTune properly installed, if you are using the CLI execute:

$ cargo build
$ vtune -run-pass-thru=--no-altstack -collect hotspots target/debug/wasmtime --vtune foo.wasm

This command tells the VTune collector (vtune) to collect hot spot profiling data as Wasmtime is executing foo.wasm. The --vtune flag enables VTune support in Wasmtime so that the collector is also alerted to JIT events that take place during runtime. The first time this is run, the result of the command is a results diretory r000hs/ which contains profiling data for Wasmtime and the execution of foo.wasm. This data can then be read and displayed via the command line or via the VTune GUI by importing the result.

Example: CLI Collection

Using a familiar algorithm, we'll start with the following Rust code:

fn main() {
    let n = 45;
    println!("fib({}) = {}", n, fib(n));
}

fn fib(n: u32) -> u32 {
    if n <= 2 {
        1
    } else {
        fib(n - 1) + fib(n - 2)
    }
}

We compile the example to Wasm:

$ rustc --target wasm32-wasi fib.rs -C opt-level=z -C lto=yes

Then we execute the Wasmtime runtime (built with the vtune feature and executed with the --vtune flag to enable reporting) inside the VTune CLI application, vtune, which must already be installed and available on the path. To collect hot spot profiling information, we execute:

$ rustc --target wasm32-wasi fib.rs -C opt-level=z -C lto=yes
$ vtune -run-pass-thru=--no-altstack -v -collect hotspots target/debug/wasmtime --vtune fib.wasm
fib(45) = 1134903170
amplxe: Collection stopped.
amplxe: Using result path /home/jlb6740/wasmtime/r000hs
amplxe: Executing actions  7 % Clearing the database
amplxe: The database has been cleared, elapsed time is 0.239 seconds.
amplxe: Executing actions 14 % Updating precomputed scalar metrics
amplxe: Raw data has been loaded to the database, elapsed time is 0.792 seconds.
amplxe: Executing actions 19 % Processing profile metrics and debug information
...
Top Hotspots
Function                                                                                      Module          CPU Time
--------------------------------------------------------------------------------------------  --------------  --------
h2bacf53cb3845acf                                                                             [Dynamic code]    3.480s
__memmove_avx_unaligned_erms                                                                  libc.so.6         0.222s
cranelift_codegen::ir::instructions::InstructionData::opcode::hee6f5b6a72fc684e               wasmtime          0.122s
core::ptr::slice_from_raw_parts::hc5cb6f1b39a0e7a1                                            wasmtime          0.066s
_$LT$usize$u20$as$u20$core..slice..SliceIndex$LT$$u5b$T$u5d$$GT$$GT$::get::h70c7f142eeeee8bd  wasmtime          0.066s

Example: Importing Results into GUI

Results directories created by the vtune CLI can be imported in the VTune GUI by clicking "Open > Result". Below is a visualization of the collected data as seen in VTune's GUI:

vtune report output

Example: GUI Collection

VTune can collect data in multiple ways (see vtune CLI discussion above); another way is to use the VTune GUI directly. A standard work flow might look like:

  • Open VTune Profiler
  • "Configure Analysis" with
    • "Application" set to /path/to/wasmtime (e.g., target/debug/wasmtime)
    • "Application parameters" set to --vtune /path/to/module.wasm
    • "Working directory" set as appropriate
    • Enable "Hardware Event-Based Sampling," which may require some system configuration, e.g. sysctl -w kernel.perf_event_paranoid=0
  • Start the analysis

Embedding in Rust

This section is intended to showcase the Rust embedding API for Wasmtime. This is done through the wasmtime crate. In addition to browsing the following examples you can also browse the full API documentation.

Hello, world!

You can also browse this source code online and clone the wasmtime repository to run the example locally.

This example shows off how to instantiate a simple wasm module and interact with it. For more information about the types used here be sure to review the core concepts of the wasmtime API as well as the general API documentation.

hello.wat

(module
  (func $hello (import "" "hello"))
  (func (export "run") (call $hello))
)

hello.rs

//! Small example of how to instantiate a wasm module that imports one function,
//! showing how you can fill in host functionality for a wasm module.

// You can execute this example with `cargo run --example hello`

use anyhow::Result;
use wasmtime::*;

struct MyState {
    name: String,
    count: usize,
}

fn main() -> Result<()> {
    // First the wasm module needs to be compiled. This is done with a global
    // "compilation environment" within an `Engine`. Note that engines can be
    // further configured through `Config` if desired instead of using the
    // default like this is here.
    println!("Compiling module...");
    let engine = Engine::default();
    let module = Module::from_file(&engine, "examples/hello.wat")?;

    // After a module is compiled we create a `Store` which will contain
    // instantiated modules and other items like host functions. A Store
    // contains an arbitrary piece of host information, and we use `MyState`
    // here.
    println!("Initializing...");
    let mut store = Store::new(
        &engine,
        MyState {
            name: "hello, world!".to_string(),
            count: 0,
        },
    );

    // Our wasm module we'll be instantiating requires one imported function.
    // the function takes no parameters and returns no results. We create a host
    // implementation of that function here, and the `caller` parameter here is
    // used to get access to our original `MyState` value.
    println!("Creating callback...");
    let hello_func = Func::wrap(&mut store, |mut caller: Caller<'_, MyState>| {
        println!("Calling back...");
        println!("> {}", caller.data().name);
        caller.data_mut().count += 1;
    });

    // Once we've got that all set up we can then move to the instantiation
    // phase, pairing together a compiled module as well as a set of imports.
    // Note that this is where the wasm `start` function, if any, would run.
    println!("Instantiating module...");
    let imports = [hello_func.into()];
    let instance = Instance::new(&mut store, &module, &imports)?;

    // Next we poke around a bit to extract the `run` function from the module.
    println!("Extracting export...");
    let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;

    // And last but not least we can call it!
    println!("Calling export...");
    run.call(&mut store, ())?;

    println!("Done.");
    Ok(())
}

Calculating the GCD

You can also browse this source code online and clone the wasmtime repository to run the example locally.

This example shows off how run a wasm program which calculates the GCD of two numbers.

gcd.wat

(module
  (func $gcd (param i32 i32) (result i32)
    (local i32)
    block  ;; label = @1
      block  ;; label = @2
        local.get 0
        br_if 0 (;@2;)
        local.get 1
        local.set 2
        br 1 (;@1;)
      end
      loop  ;; label = @2
        local.get 1
        local.get 0
        local.tee 2
        i32.rem_u
        local.set 0
        local.get 2
        local.set 1
        local.get 0
        br_if 0 (;@2;)
      end
    end
    local.get 2
  )
  (export "gcd" (func $gcd))
)

gcd.rs

//! Example of instantiating of the WebAssembly module and invoking its exported
//! function.

// You can execute this example with `cargo run --example gcd`

use anyhow::Result;
use wasmtime::*;

fn main() -> Result<()> {
    // Load our WebAssembly (parsed WAT in our case), and then load it into a
    // `Module` which is attached to a `Store` cache. After we've got that we
    // can instantiate it.
    let mut store = Store::<()>::default();
    let module = Module::from_file(store.engine(), "examples/gcd.wat")?;
    let instance = Instance::new(&mut store, &module, &[])?;

    // Invoke `gcd` export
    let gcd = instance.get_typed_func::<(i32, i32), i32>(&mut store, "gcd")?;

    println!("gcd(6, 27) = {}", gcd.call(&mut store, (6, 27))?);
    Ok(())
}

Using linear memory

You can also browse this source code online and clone the wasmtime repository to run the example locally.

This example shows off how to interact with wasm memory in a module. Be sure to read the documentation for Memory as well.

memory.wat

(module
  (memory (export "memory") 2 3)

  (func (export "size") (result i32) (memory.size))
  (func (export "load") (param i32) (result i32)
    (i32.load8_s (local.get 0))
  )
  (func (export "store") (param i32 i32)
    (i32.store8 (local.get 0) (local.get 1))
  )

  (data (i32.const 0x1000) "\01\02\03\04")
)

memory.rs

//! An example of how to interact with wasm memory.
//!
//! Here a small wasm module is used to show how memory is initialized, how to
//! read and write memory through the `Memory` object, and how wasm functions
//! can trap when dealing with out-of-bounds addresses.

// You can execute this example with `cargo run --example example`

use anyhow::Result;
use wasmtime::*;

fn main() -> Result<()> {
    // Create our `store_fn` context and then compile a module and create an
    // instance from the compiled module all in one go.
    let mut store: Store<()> = Store::default();
    let module = Module::from_file(store.engine(), "examples/memory.wat")?;
    let instance = Instance::new(&mut store, &module, &[])?;

    // load_fn up our exports from the instance
    let memory = instance
        .get_memory(&mut store, "memory")
        .ok_or(anyhow::format_err!("failed to find `memory` export"))?;
    let size = instance.get_typed_func::<(), i32>(&mut store, "size")?;
    let load_fn = instance.get_typed_func::<i32, i32>(&mut store, "load")?;
    let store_fn = instance.get_typed_func::<(i32, i32), ()>(&mut store, "store")?;

    println!("Checking memory...");
    assert_eq!(memory.size(&store), 2);
    assert_eq!(memory.data_size(&store), 0x20000);
    assert_eq!(memory.data_mut(&mut store)[0], 0);
    assert_eq!(memory.data_mut(&mut store)[0x1000], 1);
    assert_eq!(memory.data_mut(&mut store)[0x1003], 4);

    assert_eq!(size.call(&mut store, ())?, 2);
    assert_eq!(load_fn.call(&mut store, 0)?, 0);
    assert_eq!(load_fn.call(&mut store, 0x1000)?, 1);
    assert_eq!(load_fn.call(&mut store, 0x1003)?, 4);
    assert_eq!(load_fn.call(&mut store, 0x1ffff)?, 0);
    assert!(load_fn.call(&mut store, 0x20000).is_err()); // out of bounds trap

    println!("Mutating memory...");
    memory.data_mut(&mut store)[0x1003] = 5;

    store_fn.call(&mut store, (0x1002, 6))?;
    assert!(store_fn.call(&mut store, (0x20000, 0)).is_err()); // out of bounds trap

    assert_eq!(memory.data(&store)[0x1002], 6);
    assert_eq!(memory.data(&store)[0x1003], 5);
    assert_eq!(load_fn.call(&mut store, 0x1002)?, 6);
    assert_eq!(load_fn.call(&mut store, 0x1003)?, 5);

    // Grow memory.
    println!("Growing memory...");
    memory.grow(&mut store, 1)?;
    assert_eq!(memory.size(&store), 3);
    assert_eq!(memory.data_size(&store), 0x30000);

    assert_eq!(load_fn.call(&mut store, 0x20000)?, 0);
    store_fn.call(&mut store, (0x20000, 0))?;
    assert!(load_fn.call(&mut store, 0x30000).is_err());
    assert!(store_fn.call(&mut store, (0x30000, 0)).is_err());

    assert!(memory.grow(&mut store, 1).is_err());
    assert!(memory.grow(&mut store, 0).is_ok());

    println!("Creating stand-alone memory...");
    let memorytype = MemoryType::new(5, Some(5));
    let memory2 = Memory::new(&mut store, memorytype)?;
    assert_eq!(memory2.size(&store), 5);
    assert!(memory2.grow(&mut store, 1).is_err());
    assert!(memory2.grow(&mut store, 0).is_ok());

    Ok(())
}

WASI

You can also browse this source code online and clone the wasmtime repository to run the example locally.

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.

Wasm Source code

fn main() {
    println!("Hello, world!");
}

wasi.rs

//! Example of instantiating a wasm module which uses WASI imports.

/*
You can execute this example with:
    cmake example/
    cargo run --example wasi
*/

use anyhow::Result;
use wasmtime::*;
use wasmtime_wasi::sync::WasiCtxBuilder;

fn main() -> Result<()> {
    // Define the WASI functions globally on the `Config`.
    let engine = Engine::default();
    let mut linker = Linker::new(&engine);
    wasmtime_wasi::add_to_linker(&mut linker, |s| s)?;

    // Create a WASI context and put it in a Store; all instances in the store
    // share this context. `WasiCtxBuilder` provides a number of ways to
    // configure what the target program will have access to.
    let wasi = WasiCtxBuilder::new()
        .inherit_stdio()
        .inherit_args()?
        .build();
    let mut store = Store::new(&engine, wasi);

    // Instantiate our module with the imports we've created, and run it.
    let module = Module::from_file(&engine, "target/wasm32-wasi/debug/wasi.wasm")?;
    linker.module(&mut store, "", &module)?;
    linker
        .get_default(&mut store, "")?
        .typed::<(), ()>(&store)?
        .call(&mut store, ())?;

    Ok(())
}

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:

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

Linking modules

You can also browse this source code online and clone the wasmtime repository to run the example locally.

This example shows off how to compile and instantiate modules which link together. Be sure to read the API documentation for Linker as well.

linking1.wat

(module
  (import "linking2" "double" (func $double (param i32) (result i32)))
  (import "linking2" "log" (func $log (param i32 i32)))
  (import "linking2" "memory" (memory 1))
  (import "linking2" "memory_offset" (global $offset i32))

  (func (export "run")
    ;; Call into the other module to double our number, and we could print it
    ;; here but for now we just drop it
    i32.const 2
    call $double
    drop

    ;; Our `data` segment initialized our imported memory, so let's print the
    ;; string there now.
    global.get $offset
    i32.const 14
    call $log
  )

  (data (global.get $offset) "Hello, world!\n")
)

linking2.wat

(module
  (type $fd_write_ty (func (param i32 i32 i32 i32) (result i32)))
  (import "wasi_snapshot_preview1" "fd_write" (func $fd_write (type $fd_write_ty)))

  (func (export "double") (param i32) (result i32)
    local.get 0
    i32.const 2
    i32.mul
  )

  (func (export "log") (param i32 i32)
    ;; store the pointer in the first iovec field
    i32.const 4
    local.get 0
    i32.store

    ;; store the length in the first iovec field
    i32.const 4
    local.get 1
    i32.store offset=4

    ;; call the `fd_write` import
    i32.const 1     ;; stdout fd
    i32.const 4     ;; iovs start
    i32.const 1     ;; number of iovs
    i32.const 0     ;; where to write nwritten bytes
    call $fd_write
    drop
  )

  (memory (export "memory") 2)
  (global (export "memory_offset") i32 (i32.const 65536))
)

linking.rs

//! Example of instantiating two modules which link to each other.

// You can execute this example with `cargo run --example linking`

use anyhow::Result;
use wasmtime::*;
use wasmtime_wasi::sync::WasiCtxBuilder;

fn main() -> Result<()> {
    let engine = Engine::default();

    // First set up our linker which is going to be linking modules together. We
    // want our linker to have wasi available, so we set that up here as well.
    let mut linker = Linker::new(&engine);
    wasmtime_wasi::add_to_linker(&mut linker, |s| s)?;

    // Load and compile our two modules
    let linking1 = Module::from_file(&engine, "examples/linking1.wat")?;
    let linking2 = Module::from_file(&engine, "examples/linking2.wat")?;

    // Configure WASI and insert it into a `Store`
    let wasi = WasiCtxBuilder::new()
        .inherit_stdio()
        .inherit_args()?
        .build();
    let mut store = Store::new(&engine, wasi);

    // Instantiate our first module which only uses WASI, then register that
    // instance with the linker since the next linking will use it.
    let linking2 = linker.instantiate(&mut store, &linking2)?;
    linker.instance(&mut store, "linking2", linking2)?;

    // And with that we can perform the final link and the execute the module.
    let linking1 = linker.instantiate(&mut store, &linking1)?;
    let run = linking1.get_typed_func::<(), ()>(&mut store, "run")?;
    run.call(&mut store, ())?;
    Ok(())
}

Debugging

You can also browse this source code online and clone the wasmtime repository to run the example locally.

This example shows off how to set up a module for dynamic runtime debugging via a native debugger like GDB or LLDB.

main.rs

//! Example of enabling debuginfo for wasm code which allows interactive
//! debugging of the wasm code. When using recent versions of LLDB
//! you can debug this executable and set breakpoints in wasm code and look at
//! the rust source code as input.

// To execute this example you'll need to run two commands:
//
//      cargo build -p example-fib-debug-wasm --target wasm32-unknown-unknown
//      cargo run --example fib-debug

use anyhow::Result;
use wasmtime::*;

fn main() -> Result<()> {
    // Load our previously compiled wasm file (built previously with Cargo) and
    // also ensure that we generate debuginfo so this executable can be
    // debugged in GDB.
    let engine = Engine::new(Config::new().debug_info(true))?;
    let mut store = Store::new(&engine, ());
    let module = Module::from_file(&engine, "target/wasm32-unknown-unknown/debug/fib.wasm")?;
    let instance = Instance::new(&mut store, &module, &[])?;

    // Invoke `fib` export
    let fib = instance.get_typed_func::<i32, i32>(&mut store, "fib")?;
    println!("fib(6) = {}", fib.call(&mut store, 6)?);
    Ok(())
}

Using multi-value

You can also browse this source code online and clone the wasmtime repository to run the example locally.

This example shows off how to interact with a wasm module that uses multi-value exports and imports.

multi.wat

(module
  (func $f (import "" "f") (param i32 i64) (result i64 i32))

  (func $g (export "g") (param i32 i64) (result i64 i32)
    (call $f (local.get 0) (local.get 1))
  )

  (func $round_trip_many
    (export "round_trip_many")
    (param i64 i64 i64 i64 i64 i64 i64 i64 i64 i64)
    (result i64 i64 i64 i64 i64 i64 i64 i64 i64 i64)

    local.get 0
    local.get 1
    local.get 2
    local.get 3
    local.get 4
    local.get 5
    local.get 6
    local.get 7
    local.get 8
    local.get 9)
)

multi.rs

//! This is an example of working with multi-value modules and dealing with
//! multi-value functions.
//!
//! Note that the `Func::wrap*` interfaces cannot be used to return multiple
//! values just yet, so we need to use the more dynamic `Func::new` and
//! `Func::call` methods.

// You can execute this example with `cargo run --example multi`

use anyhow::Result;

fn main() -> Result<()> {
    use wasmtime::*;

    println!("Initializing...");
    let engine = Engine::default();
    let mut store = Store::new(&engine, ());

    // Compile.
    println!("Compiling module...");
    let module = Module::from_file(&engine, "examples/multi.wat")?;

    // Create a host function which takes multiple parameters and returns
    // multiple results.
    println!("Creating callback...");
    let callback_func = Func::wrap(&mut store, |a: i32, b: i64| -> (i64, i32) {
        (b + 1, a + 1)
    });

    // Instantiate.
    println!("Instantiating module...");
    let instance = Instance::new(&mut store, &module, &[callback_func.into()])?;

    // Extract exports.
    println!("Extracting export...");
    let g = instance.get_typed_func::<(i32, i64), (i64, i32)>(&mut store, "g")?;

    // Call `$g`.
    println!("Calling export \"g\"...");
    let (a, b) = g.call(&mut store, (1, 3))?;

    println!("Printing result...");
    println!("> {} {}", a, b);

    assert_eq!(a, 4);
    assert_eq!(b, 2);

    // Call `$round_trip_many`.
    println!("Calling export \"round_trip_many\"...");
    let round_trip_many = instance
        .get_typed_func::<
        (i64, i64, i64, i64, i64, i64, i64, i64, i64, i64),
        (i64, i64, i64, i64, i64, i64, i64, i64, i64, i64),
        >
        (&mut store, "round_trip_many")?;
    let results = round_trip_many.call(&mut store, (0, 1, 2, 3, 4, 5, 6, 7, 8, 9))?;

    println!("Printing result...");
    println!("> {:?}", results);
    assert_eq!(results, (0, 1, 2, 3, 4, 5, 6, 7, 8, 9));

    Ok(())
}

Embedding in C

This section is intended to showcase the C embedding API for Wasmtime. Full reference documentation for the C API can be found online

Hello, world!

You can also browse this source code online and clone the wasmtime repository to run the example locally.

This example shows off how to instantiate a simple wasm module and interact with it.

hello.wat

(module
  (func $hello (import "" "hello"))
  (func (export "run") (call $hello))
)

hello.c

/*
Example of instantiating of the WebAssembly module and invoking its exported
function.

You can compile and run this example on Linux with:

   cargo build --release -p wasmtime-c-api
   cc examples/hello.c \
       -I crates/c-api/include \
       -I crates/c-api/wasm-c-api/include \
       target/release/libwasmtime.a \
       -lpthread -ldl -lm \
       -o hello
   ./hello

Note that on Windows and macOS the command will be similar, but you'll need
to tweak the `-lpthread` and such annotations as well as the name of the
`libwasmtime.a` file on Windows.

You can also build using cmake:

mkdir build && cd build && cmake .. && cmake --build . --target wasmtime-hello
*/

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <wasm.h>
#include <wasmtime.h>

static void exit_with_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap);

static wasm_trap_t* hello_callback(
    void *env,
    wasmtime_caller_t *caller,
    const wasmtime_val_t *args,
    size_t nargs,
    wasmtime_val_t *results,
    size_t nresults
) {
  printf("Calling back...\n");
  printf("> Hello World!\n");
  return NULL;
}

int main() {
  int ret = 0;
  // Set up our compilation context. Note that we could also work with a
  // `wasm_config_t` here to configure what feature are enabled and various
  // compilation settings.
  printf("Initializing...\n");
  wasm_engine_t *engine = wasm_engine_new();
  assert(engine != NULL);

  // With an engine we can create a *store* which is a long-lived group of wasm
  // modules. Note that we allocate some custom data here to live in the store,
  // but here we skip that and specify NULL.
  wasmtime_store_t *store = wasmtime_store_new(engine, NULL, NULL);
  assert(store != NULL);
  wasmtime_context_t *context = wasmtime_store_context(store);

  // Read our input file, which in this case is a wasm text file.
  FILE* file = fopen("examples/hello.wat", "r");
  assert(file != NULL);
  fseek(file, 0L, SEEK_END);
  size_t file_size = ftell(file);
  fseek(file, 0L, SEEK_SET);
  wasm_byte_vec_t wat;
  wasm_byte_vec_new_uninitialized(&wat, file_size);
  assert(fread(wat.data, file_size, 1, file) == 1);
  fclose(file);

  // Parse the wat into the binary wasm format
  wasm_byte_vec_t wasm;
  wasmtime_error_t *error = wasmtime_wat2wasm(wat.data, wat.size, &wasm);
  if (error != NULL)
    exit_with_error("failed to parse wat", error, NULL);
  wasm_byte_vec_delete(&wat);

  // Now that we've got our binary webassembly we can compile our module.
  printf("Compiling module...\n");
  wasmtime_module_t *module = NULL;
  error = wasmtime_module_new(engine, (uint8_t*) wasm.data, wasm.size, &module);
  wasm_byte_vec_delete(&wasm);
  if (error != NULL)
    exit_with_error("failed to compile module", error, NULL);

  // Next up we need to create the function that the wasm module imports. Here
  // we'll be hooking up a thunk function to the `hello_callback` native
  // function above. Note that we can assign custom data, but we just use NULL
  // for now).
  printf("Creating callback...\n");
  wasm_functype_t *hello_ty = wasm_functype_new_0_0();
  wasmtime_func_t hello;
  wasmtime_func_new(context, hello_ty, hello_callback, NULL, NULL, &hello);

  // With our callback function we can now instantiate the compiled module,
  // giving us an instance we can then execute exports from. Note that
  // instantiation can trap due to execution of the `start` function, so we need
  // to handle that here too.
  printf("Instantiating module...\n");
  wasm_trap_t *trap = NULL;
  wasmtime_instance_t instance;
  wasmtime_extern_t import;
  import.kind = WASMTIME_EXTERN_FUNC;
  import.of.func = hello;
  error = wasmtime_instance_new(context, module, &import, 1, &instance, &trap);
  if (error != NULL || trap != NULL)
    exit_with_error("failed to instantiate", error, trap);

  // Lookup our `run` export function
  printf("Extracting export...\n");
  wasmtime_extern_t run;
  bool ok = wasmtime_instance_export_get(context, &instance, "run", 3, &run);
  assert(ok);
  assert(run.kind == WASMTIME_EXTERN_FUNC);

  // And call it!
  printf("Calling export...\n");
  error = wasmtime_func_call(context, &run.of.func, NULL, 0, NULL, 0, &trap);
  if (error != NULL || trap != NULL)
    exit_with_error("failed to call function", error, trap);

  // Clean up after ourselves at this point
  printf("All finished!\n");
  ret = 0;

  wasmtime_module_delete(module);
  wasmtime_store_delete(store);
  wasm_engine_delete(engine);
  return ret;
}

static void exit_with_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap) {
  fprintf(stderr, "error: %s\n", message);
  wasm_byte_vec_t error_message;
  if (error != NULL) {
    wasmtime_error_message(error, &error_message);
    wasmtime_error_delete(error);
  } else {
    wasm_trap_message(trap, &error_message);
    wasm_trap_delete(trap);
  }
  fprintf(stderr, "%.*s\n", (int) error_message.size, error_message.data);
  wasm_byte_vec_delete(&error_message);
  exit(1);
}

Calculating the GCD

You can also browse this source code online and clone the wasmtime repository to run the example locally.

This example shows off how run a wasm program which calculates the GCD of two numbers.

gcd.wat

(module
  (func $gcd (param i32 i32) (result i32)
    (local i32)
    block  ;; label = @1
      block  ;; label = @2
        local.get 0
        br_if 0 (;@2;)
        local.get 1
        local.set 2
        br 1 (;@1;)
      end
      loop  ;; label = @2
        local.get 1
        local.get 0
        local.tee 2
        i32.rem_u
        local.set 0
        local.get 2
        local.set 1
        local.get 0
        br_if 0 (;@2;)
      end
    end
    local.get 2
  )
  (export "gcd" (func $gcd))
)

gcd.c

/*
Example of instantiating of the WebAssembly module and invoking its exported
function.

You can compile and run this example on Linux with:

   cargo build --release -p wasmtime-c-api
   cc examples/gcd.c \
       -I crates/c-api/include \
       -I crates/c-api/wasm-c-api/include \
       target/release/libwasmtime.a \
       -lpthread -ldl -lm \
       -o gcd
   ./gcd

Note that on Windows and macOS the command will be similar, but you'll need
to tweak the `-lpthread` and such annotations.

You can also build using cmake:

mkdir build && cd build && cmake .. && cmake --build . --target wasmtime-gcd
*/

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <wasm.h>
#include <wasmtime.h>

static void exit_with_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap);

int main() {
  int ret = 0;
  // Set up our context
  wasm_engine_t *engine = wasm_engine_new();
  assert(engine != NULL);
  wasmtime_store_t *store = wasmtime_store_new(engine, NULL, NULL);
  assert(store != NULL);
  wasmtime_context_t *context = wasmtime_store_context(store);

  // Load our input file to parse it next
  FILE* file = fopen("examples/gcd.wat", "r");
  if (!file) {
    printf("> Error loading file!\n");
    return 1;
  }
  fseek(file, 0L, SEEK_END);
  size_t file_size = ftell(file);
  fseek(file, 0L, SEEK_SET);
  wasm_byte_vec_t wat;
  wasm_byte_vec_new_uninitialized(&wat, file_size);
  if (fread(wat.data, file_size, 1, file) != 1) {
    printf("> Error loading module!\n");
    return 1;
  }
  fclose(file);

  // Parse the wat into the binary wasm format
  wasm_byte_vec_t wasm;
  wasmtime_error_t *error = wasmtime_wat2wasm(wat.data, wat.size, &wasm);
  if (error != NULL)
    exit_with_error("failed to parse wat", error, NULL);
  wasm_byte_vec_delete(&wat);

  // Compile and instantiate our module
  wasmtime_module_t *module = NULL;
  error = wasmtime_module_new(engine, (uint8_t*) wasm.data, wasm.size, &module);
  if (module == NULL)
    exit_with_error("failed to compile module", error, NULL);
  wasm_byte_vec_delete(&wasm);

  wasm_trap_t *trap = NULL;
  wasmtime_instance_t instance;
  error = wasmtime_instance_new(context, module, NULL, 0, &instance, &trap);
  if (error != NULL || trap != NULL)
    exit_with_error("failed to instantiate", error, trap);

  // Lookup our `gcd` export function
  wasmtime_extern_t gcd;
  bool ok = wasmtime_instance_export_get(context, &instance, "gcd", 3, &gcd);
  assert(ok);
  assert(gcd.kind == WASMTIME_EXTERN_FUNC);

  // And call it!
  int a = 6;
  int b = 27;
  wasmtime_val_t params[2];
  params[0].kind = WASMTIME_I32;
  params[0].of.i32 = a;
  params[1].kind = WASMTIME_I32;
  params[1].of.i32 = b;
  wasmtime_val_t results[1];
  error = wasmtime_func_call(context, &gcd.of.func, params, 2, results, 1, &trap);
  if (error != NULL || trap != NULL)
    exit_with_error("failed to call gcd", error, trap);
  assert(results[0].kind == WASMTIME_I32);

  printf("gcd(%d, %d) = %d\n", a, b, results[0].of.i32);

  // Clean up after ourselves at this point
  ret = 0;

  wasmtime_module_delete(module);
  wasmtime_store_delete(store);
  wasm_engine_delete(engine);
  return ret;
}

static void exit_with_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap) {
  fprintf(stderr, "error: %s\n", message);
  wasm_byte_vec_t error_message;
  if (error != NULL) {
    wasmtime_error_message(error, &error_message);
  } else {
    wasm_trap_message(trap, &error_message);
  }
  fprintf(stderr, "%.*s\n", (int) error_message.size, error_message.data);
  wasm_byte_vec_delete(&error_message);
  exit(1);
}

Using linear memory

You can also browse this source code online and clone the wasmtime repository to run the example locally.

This example shows off how to interact with wasm memory in a module. Be sure to read the documentation for Memory as well.

memory.wat

(module
  (memory (export "memory") 2 3)

  (func (export "size") (result i32) (memory.size))
  (func (export "load") (param i32) (result i32)
    (i32.load8_s (local.get 0))
  )
  (func (export "store") (param i32 i32)
    (i32.store8 (local.get 0) (local.get 1))
  )

  (data (i32.const 0x1000) "\01\02\03\04")
)

memory.c

/*
Example of instantiating of the WebAssembly module and invoking its exported
function.

You can compile and run this example on Linux with:

   cargo build --release -p wasmtime-c-api
   cc examples/memory.c \
       -I crates/c-api/include \
       -I crates/c-api/wasm-c-api/include \
       target/release/libwasmtime.a \
       -lpthread -ldl -lm \
       -o memory
   ./memory

Note that on Windows and macOS the command will be similar, but you'll need
to tweak the `-lpthread` and such annotations.

You can also build using cmake:

mkdir build && cd build && cmake .. && cmake --build . --target wasmtime-memory

Also note that this example was taken from
https://github.com/WebAssembly/wasm-c-api/blob/master/example/memory.c
originally
*/

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wasm.h>
#include <wasmtime.h>

static void exit_with_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap);

void check(bool success) {
  if (!success) {
    printf("> Error, expected success\n");
    exit(1);
  }
}

void check_call(wasmtime_context_t *store,
                wasmtime_func_t *func,
                const wasmtime_val_t* args,
                size_t nargs,
                int32_t expected) {
  wasmtime_val_t results[1];
  wasm_trap_t *trap = NULL;
  wasmtime_error_t *error = wasmtime_func_call(
      store, func, args, nargs, results, 1, &trap
  );
  if (error != NULL || trap != NULL)
    exit_with_error("failed to call function", error, trap);
  if (results[0].of.i32 != expected) {
    printf("> Error on result\n");
    exit(1);
  }
}

void check_call0(wasmtime_context_t *store, wasmtime_func_t *func, int32_t expected) {
  check_call(store, func, NULL, 0, expected);
}

void check_call1(wasmtime_context_t *store, wasmtime_func_t *func, int32_t arg, int32_t expected) {
  wasmtime_val_t args[1];
  args[0].kind = WASMTIME_I32;
  args[0].of.i32 = arg;
  check_call(store, func, args, 1, expected);
}

void check_call2(wasmtime_context_t *store, wasmtime_func_t *func, int32_t arg1, int32_t arg2, int32_t expected) {
  wasmtime_val_t args[2];
  args[0].kind = WASMTIME_I32;
  args[0].of.i32 = arg1;
  args[1].kind = WASMTIME_I32;
  args[1].of.i32 = arg2;
  check_call(store, func, args, 2, expected);
}

void check_ok(wasmtime_context_t *store, wasmtime_func_t *func, const wasmtime_val_t* args, size_t nargs) {
  wasm_trap_t *trap = NULL;
  wasmtime_error_t *error = wasmtime_func_call(store, func, args, nargs, NULL, 0, &trap);
  if (error != NULL || trap != NULL)
    exit_with_error("failed to call function", error, trap);
}

void check_ok2(wasmtime_context_t *store, wasmtime_func_t *func, int32_t arg1, int32_t arg2) {
  wasmtime_val_t args[2];
  args[0].kind = WASMTIME_I32;
  args[0].of.i32 = arg1;
  args[1].kind = WASMTIME_I32;
  args[1].of.i32 = arg2;
  check_ok(store, func, args, 2);
}

void check_trap(wasmtime_context_t *store,
                wasmtime_func_t *func,
                const wasmtime_val_t *args,
                size_t nargs,
                size_t num_results) {
  assert(num_results <= 1);
  wasmtime_val_t results[1];
  wasm_trap_t *trap = NULL;
  wasmtime_error_t *error = wasmtime_func_call(store, func, args, nargs, results, num_results, &trap);
  if (error != NULL)
    exit_with_error("failed to call function", error, NULL);
  if (trap == NULL) {
    printf("> Error on result, expected trap\n");
    exit(1);
  }
  wasm_trap_delete(trap);
}

void check_trap1(wasmtime_context_t *store, wasmtime_func_t *func, int32_t arg) {
  wasmtime_val_t args[1];
  args[0].kind = WASMTIME_I32;
  args[0].of.i32 = arg;
  check_trap(store, func, args, 1, 1);
}

void check_trap2(wasmtime_context_t *store, wasmtime_func_t *func, int32_t arg1, int32_t arg2) {
  wasmtime_val_t args[2];
  args[0].kind = WASMTIME_I32;
  args[0].of.i32 = arg1;
  args[1].kind = WASMTIME_I32;
  args[1].of.i32 = arg2;
  check_trap(store, func, args, 2, 0);
}

int main(int argc, const char* argv[]) {
  // Initialize.
  printf("Initializing...\n");
  wasm_engine_t* engine = wasm_engine_new();
  wasmtime_store_t* store = wasmtime_store_new(engine, NULL, NULL);
  wasmtime_context_t *context = wasmtime_store_context(store);

  // Load our input file to parse it next
  FILE* file = fopen("examples/memory.wat", "r");
  if (!file) {
    printf("> Error loading file!\n");
    return 1;
  }
  fseek(file, 0L, SEEK_END);
  size_t file_size = ftell(file);
  fseek(file, 0L, SEEK_SET);
  wasm_byte_vec_t wat;
  wasm_byte_vec_new_uninitialized(&wat, file_size);
  if (fread(wat.data, file_size, 1, file) != 1) {
    printf("> Error loading module!\n");
    return 1;
  }
  fclose(file);

  // Parse the wat into the binary wasm format
  wasm_byte_vec_t binary;
  wasmtime_error_t *error = wasmtime_wat2wasm(wat.data, wat.size, &binary);
  if (error != NULL)
    exit_with_error("failed to parse wat", error, NULL);
  wasm_byte_vec_delete(&wat);

  // Compile.
  printf("Compiling module...\n");
  wasmtime_module_t* module = NULL;
  error = wasmtime_module_new(engine, (uint8_t*) binary.data, binary.size, &module);
  if (error)
    exit_with_error("failed to compile module", error, NULL);
  wasm_byte_vec_delete(&binary);

  // Instantiate.
  printf("Instantiating module...\n");
  wasmtime_instance_t instance;
  wasm_trap_t *trap = NULL;
  error = wasmtime_instance_new(context, module, NULL, 0, &instance, &trap);
  if (error != NULL || trap != NULL)
    exit_with_error("failed to instantiate", error, trap);
  wasmtime_module_delete(module);

  // Extract export.
  printf("Extracting exports...\n");
  wasmtime_memory_t memory;
  wasmtime_func_t size_func, load_func, store_func;
  wasmtime_extern_t item;
  bool ok;
  ok = wasmtime_instance_export_get(context, &instance, "memory", strlen("memory"), &item);
  assert(ok && item.kind == WASMTIME_EXTERN_MEMORY);
  memory = item.of.memory;
  ok = wasmtime_instance_export_get(context, &instance, "size", strlen("size"), &item);
  assert(ok && item.kind == WASMTIME_EXTERN_FUNC);
  size_func = item.of.func;
  ok = wasmtime_instance_export_get(context, &instance, "load", strlen("load"), &item);
  assert(ok && item.kind == WASMTIME_EXTERN_FUNC);
  load_func = item.of.func;
  ok = wasmtime_instance_export_get(context, &instance, "store", strlen("store"), &item);
  assert(ok && item.kind == WASMTIME_EXTERN_FUNC);
  store_func = item.of.func;

  // Check initial memory.
  printf("Checking memory...\n");
  check(wasmtime_memory_size(context, &memory) == 2);
  check(wasmtime_memory_data_size(context, &memory) == 0x20000);
  check(wasmtime_memory_data(context, &memory)[0] == 0);
  check(wasmtime_memory_data(context, &memory)[0x1000] == 1);
  check(wasmtime_memory_data(context, &memory)[0x1003] == 4);

  check_call0(context, &size_func, 2);
  check_call1(context, &load_func, 0, 0);
  check_call1(context, &load_func, 0x1000, 1);
  check_call1(context, &load_func, 0x1003, 4);
  check_call1(context, &load_func, 0x1ffff, 0);
  check_trap1(context, &load_func, 0x20000);

  // Mutate memory.
  printf("Mutating memory...\n");
  wasmtime_memory_data(context, &memory)[0x1003] = 5;
  check_ok2(context, &store_func, 0x1002, 6);
  check_trap2(context, &store_func, 0x20000, 0);

  check(wasmtime_memory_data(context, &memory)[0x1002] == 6);
  check(wasmtime_memory_data(context, &memory)[0x1003] == 5);
  check_call1(context, &load_func, 0x1002, 6);
  check_call1(context, &load_func, 0x1003, 5);

  // Grow memory.
  printf("Growing memory...\n");
  uint64_t old_size;
  error = wasmtime_memory_grow(context, &memory, 1, &old_size);
  if (error != NULL)
    exit_with_error("failed to grow memory", error, trap);
  check(wasmtime_memory_size(context, &memory) == 3);
  check(wasmtime_memory_data_size(context, &memory) == 0x30000);

  check_call1(context, &load_func, 0x20000, 0);
  check_ok2(context, &store_func, 0x20000, 0);
  check_trap1(context, &load_func, 0x30000);
  check_trap2(context, &store_func, 0x30000, 0);

  error = wasmtime_memory_grow(context, &memory, 1, &old_size);
  assert(error != NULL);
  wasmtime_error_delete(error);
  error = wasmtime_memory_grow(context, &memory, 0, &old_size);
  if (error != NULL)
    exit_with_error("failed to grow memory", error, trap);

  // Create stand-alone memory.
  printf("Creating stand-alone memory...\n");
  wasm_limits_t limits = {5, 5};
  wasm_memorytype_t* memorytype = wasm_memorytype_new(&limits);
  wasmtime_memory_t memory2;
  error = wasmtime_memory_new(context, memorytype, &memory2);
  if (error != NULL)
    exit_with_error("failed to create memory", error, trap);
  wasm_memorytype_delete(memorytype);
  check(wasmtime_memory_size(context, &memory2) == 5);

  // Shut down.
  printf("Shutting down...\n");
  wasmtime_store_delete(store);
  wasm_engine_delete(engine);

  // All done.
  printf("Done.\n");
  return 0;
}

static void exit_with_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap) {
  fprintf(stderr, "error: %s\n", message);
  wasm_byte_vec_t error_message;
  if (error != NULL) {
    wasmtime_error_message(error, &error_message);
    wasmtime_error_delete(error);
  } else {
    wasm_trap_message(trap, &error_message);
    wasm_trap_delete(trap);
  }
  fprintf(stderr, "%.*s\n", (int) error_message.size, error_message.data);
  wasm_byte_vec_delete(&error_message);
  exit(1);
}

WASI

You can also browse this source code online and clone the wasmtime repository to run the example locally.

This example shows off how to instantiate a wasm module using WASI imports.

Wasm Source code

fn main() {
    println!("Hello, world!");
}

wasi.c

/*
Example of instantiating a WebAssembly which uses WASI imports.

You can compile and run this example on Linux with:

   cmake example/
   cargo build --release -p wasmtime-c-api
   cc examples/wasi/main.c \
       -I crates/c-api/include \
       -I crates/c-api/wasm-c-api/include \
       target/release/libwasmtime.a \
       -lpthread -ldl -lm \
       -o wasi
   ./wasi

Note that on Windows and macOS the command will be similar, but you'll need
to tweak the `-lpthread` and such annotations.

You can also build using cmake:

mkdir build && cd build && cmake .. && cmake --build . --target wasmtime-wasi
*/

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <wasm.h>
#include <wasi.h>
#include <wasmtime.h>

#define MIN(a, b) ((a) < (b) ? (a) : (b))

static void exit_with_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap);

int main() {
  // Set up our context
  wasm_engine_t *engine = wasm_engine_new();
  assert(engine != NULL);
  wasmtime_store_t *store = wasmtime_store_new(engine, NULL, NULL);
  assert(store != NULL);
  wasmtime_context_t *context = wasmtime_store_context(store);

  // Create a linker with WASI functions defined
  wasmtime_linker_t *linker = wasmtime_linker_new(engine);
  wasmtime_error_t *error = wasmtime_linker_define_wasi(linker);
  if (error != NULL)
    exit_with_error("failed to link wasi", error, NULL);

  wasm_byte_vec_t wasm;
  // Load our input file to parse it next
  FILE* file = fopen("target/wasm32-wasi/debug/wasi.wasm", "rb");
  if (!file) {
    printf("> Error loading file!\n");
    exit(1);
  }
  fseek(file, 0L, SEEK_END);
  size_t file_size = ftell(file);
  wasm_byte_vec_new_uninitialized(&wasm, file_size);
  fseek(file, 0L, SEEK_SET);
  if (fread(wasm.data, file_size, 1, file) != 1) {
    printf("> Error loading module!\n");
    exit(1);
  }
  fclose(file);

  // Compile our modules
  wasmtime_module_t *module = NULL;
  error = wasmtime_module_new(engine, (uint8_t*)wasm.data, wasm.size, &module);
  if (!module)
    exit_with_error("failed to compile module", error, NULL);
  wasm_byte_vec_delete(&wasm);

  // Instantiate wasi
  wasi_config_t *wasi_config = wasi_config_new();
  assert(wasi_config);
  wasi_config_inherit_argv(wasi_config);
  wasi_config_inherit_env(wasi_config);
  wasi_config_inherit_stdin(wasi_config);
  wasi_config_inherit_stdout(wasi_config);
  wasi_config_inherit_stderr(wasi_config);
  wasm_trap_t *trap = NULL;
  error = wasmtime_context_set_wasi(context, wasi_config);
  if (error != NULL)
    exit_with_error("failed to instantiate WASI", error, NULL);

  // Instantiate the module
  error = wasmtime_linker_module(linker, context, "", 0, module);
  if (error != NULL)
    exit_with_error("failed to instantiate module", error, NULL);

  // Run it.
  wasmtime_func_t func;
  error = wasmtime_linker_get_default(linker, context, "", 0, &func);
  if (error != NULL)
    exit_with_error("failed to locate default export for module", error, NULL);

  error = wasmtime_func_call(context, &func, NULL, 0, NULL, 0, &trap);
  if (error != NULL || trap != NULL)
    exit_with_error("error calling default export", error, trap);

  // Clean up after ourselves at this point
  wasmtime_module_delete(module);
  wasmtime_store_delete(store);
  wasm_engine_delete(engine);
  return 0;
}

static void exit_with_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap) {
  fprintf(stderr, "error: %s\n", message);
  wasm_byte_vec_t error_message;
  if (error != NULL) {
    wasmtime_error_message(error, &error_message);
    wasmtime_error_delete(error);
  } else {
    wasm_trap_message(trap, &error_message);
    wasm_trap_delete(trap);
  }
  fprintf(stderr, "%.*s\n", (int) error_message.size, error_message.data);
  wasm_byte_vec_delete(&error_message);
  exit(1);
}

Linking modules

You can also browse this source code online and clone the wasmtime repository to run the example locally.

This example shows off how to compile and instantiate modules which link together.

linking1.wat

(module
  (import "linking2" "double" (func $double (param i32) (result i32)))
  (import "linking2" "log" (func $log (param i32 i32)))
  (import "linking2" "memory" (memory 1))
  (import "linking2" "memory_offset" (global $offset i32))

  (func (export "run")
    ;; Call into the other module to double our number, and we could print it
    ;; here but for now we just drop it
    i32.const 2
    call $double
    drop

    ;; Our `data` segment initialized our imported memory, so let's print the
    ;; string there now.
    global.get $offset
    i32.const 14
    call $log
  )

  (data (global.get $offset) "Hello, world!\n")
)

linking2.wat

(module
  (type $fd_write_ty (func (param i32 i32 i32 i32) (result i32)))
  (import "wasi_snapshot_preview1" "fd_write" (func $fd_write (type $fd_write_ty)))

  (func (export "double") (param i32) (result i32)
    local.get 0
    i32.const 2
    i32.mul
  )

  (func (export "log") (param i32 i32)
    ;; store the pointer in the first iovec field
    i32.const 4
    local.get 0
    i32.store

    ;; store the length in the first iovec field
    i32.const 4
    local.get 1
    i32.store offset=4

    ;; call the `fd_write` import
    i32.const 1     ;; stdout fd
    i32.const 4     ;; iovs start
    i32.const 1     ;; number of iovs
    i32.const 0     ;; where to write nwritten bytes
    call $fd_write
    drop
  )

  (memory (export "memory") 2)
  (global (export "memory_offset") i32 (i32.const 65536))
)

linking.c

/*
Example of compiling, instantiating, and linking two WebAssembly modules
together.

You can compile and run this example on Linux with:

   cargo build --release -p wasmtime-c-api
   cc examples/linking.c \
       -I crates/c-api/include \
       -I crates/c-api/wasm-c-api/include \
       target/release/libwasmtime.a \
       -lpthread -ldl -lm \
       -o linking
   ./linking

Note that on Windows and macOS the command will be similar, but you'll need
to tweak the `-lpthread` and such annotations.

You can also build using cmake:

mkdir build && cd build && cmake .. && cmake --build . --target wasmtime-linking
*/

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <wasm.h>
#include <wasi.h>
#include <wasmtime.h>

#define MIN(a, b) ((a) < (b) ? (a) : (b))

static void exit_with_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap);
static void read_wat_file(wasm_engine_t *engine, wasm_byte_vec_t *bytes, const char *file);

int main() {
  // Set up our context
  wasm_engine_t *engine = wasm_engine_new();
  assert(engine != NULL);
  wasmtime_store_t *store = wasmtime_store_new(engine, NULL, NULL);
  assert(store != NULL);
  wasmtime_context_t *context = wasmtime_store_context(store);

  wasm_byte_vec_t linking1_wasm, linking2_wasm;
  read_wat_file(engine, &linking1_wasm, "examples/linking1.wat");
  read_wat_file(engine, &linking2_wasm, "examples/linking2.wat");

  // Compile our two modules
  wasmtime_error_t *error;
  wasmtime_module_t *linking1_module = NULL;
  wasmtime_module_t *linking2_module = NULL;
  error = wasmtime_module_new(engine, (uint8_t*) linking1_wasm.data, linking1_wasm.size, &linking1_module);
  if (error != NULL)
    exit_with_error("failed to compile linking1", error, NULL);
  error = wasmtime_module_new(engine, (uint8_t*) linking2_wasm.data, linking2_wasm.size, &linking2_module);
  if (error != NULL)
    exit_with_error("failed to compile linking2", error, NULL);
  wasm_byte_vec_delete(&linking1_wasm);
  wasm_byte_vec_delete(&linking2_wasm);

  // Configure WASI and store it within our `wasmtime_store_t`
  wasi_config_t *wasi_config = wasi_config_new();
  assert(wasi_config);
  wasi_config_inherit_argv(wasi_config);
  wasi_config_inherit_env(wasi_config);
  wasi_config_inherit_stdin(wasi_config);
  wasi_config_inherit_stdout(wasi_config);
  wasi_config_inherit_stderr(wasi_config);
  wasm_trap_t *trap = NULL;
  error = wasmtime_context_set_wasi(context, wasi_config);
  if (error != NULL)
    exit_with_error("failed to instantiate wasi", NULL, trap);

  // Create our linker which will be linking our modules together, and then add
  // our WASI instance to it.
  wasmtime_linker_t *linker = wasmtime_linker_new(engine);
  error = wasmtime_linker_define_wasi(linker);
  if (error != NULL)
    exit_with_error("failed to link wasi", error, NULL);

  // Instantiate `linking2` with our linker.
  wasmtime_instance_t linking2;
  error = wasmtime_linker_instantiate(linker, context, linking2_module, &linking2, &trap);
  if (error != NULL || trap != NULL)
    exit_with_error("failed to instantiate linking2", error, trap);

  // Register our new `linking2` instance with the linker
  error = wasmtime_linker_define_instance(linker, context, "linking2", strlen("linking2"), &linking2);
  if (error != NULL)
    exit_with_error("failed to link linking2", error, NULL);

  // Instantiate `linking1` with the linker now that `linking2` is defined
  wasmtime_instance_t linking1;
  error = wasmtime_linker_instantiate(linker, context, linking1_module, &linking1, &trap);
  if (error != NULL || trap != NULL)
    exit_with_error("failed to instantiate linking1", error, trap);

  // Lookup our `run` export function
  wasmtime_extern_t run;
  bool ok = wasmtime_instance_export_get(context, &linking1, "run", 3, &run);
  assert(ok);
  assert(run.kind == WASMTIME_EXTERN_FUNC);
  error = wasmtime_func_call(context, &run.of.func, NULL, 0, NULL, 0, &trap);
  if (error != NULL || trap != NULL)
    exit_with_error("failed to call run", error, trap);

  // Clean up after ourselves at this point
  wasmtime_linker_delete(linker);
  wasmtime_module_delete(linking1_module);
  wasmtime_module_delete(linking2_module);
  wasmtime_store_delete(store);
  wasm_engine_delete(engine);
  return 0;
}

static void read_wat_file(
  wasm_engine_t *engine,
  wasm_byte_vec_t *bytes,
  const char *filename
) {
  wasm_byte_vec_t wat;
  // Load our input file to parse it next
  FILE* file = fopen(filename, "r");
  if (!file) {
    printf("> Error loading file!\n");
    exit(1);
  }
  fseek(file, 0L, SEEK_END);
  size_t file_size = ftell(file);
  wasm_byte_vec_new_uninitialized(&wat, file_size);
  fseek(file, 0L, SEEK_SET);
  if (fread(wat.data, file_size, 1, file) != 1) {
    printf("> Error loading module!\n");
    exit(1);
  }
  fclose(file);

  // Parse the wat into the binary wasm format
  wasmtime_error_t *error = wasmtime_wat2wasm(wat.data, wat.size, bytes);
  if (error != NULL)
    exit_with_error("failed to parse wat", error, NULL);
  wasm_byte_vec_delete(&wat);
}

static void exit_with_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap) {
  fprintf(stderr, "error: %s\n", message);
  wasm_byte_vec_t error_message;
  if (error != NULL) {
    wasmtime_error_message(error, &error_message);
    wasmtime_error_delete(error);
  } else {
    wasm_trap_message(trap, &error_message);
    wasm_trap_delete(trap);
  }
  fprintf(stderr, "%.*s\n", (int) error_message.size, error_message.data);
  wasm_byte_vec_delete(&error_message);
  exit(1);
}

Debugging

You can also browse this source code online and clone the wasmtime repository to run the example locally.

This example shows off how to set up a module for dynamic runtime debugging via a native debugger like GDB or LLDB.

main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <wasm.h>
#include <wasmtime.h>

#define own

static void exit_with_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap);

int main(int argc, const char* argv[]) {
  // Configuring engine to support generating of DWARF info.
  // lldb can be used to attach to the program and observe
  // original fib-wasm.c source code and variables.
  wasm_config_t* config = wasm_config_new();
  wasmtime_config_debug_info_set(config, true);

  // Initialize.
  printf("Initializing...\n");
  wasm_engine_t* engine = wasm_engine_new_with_config(config);
  wasmtime_store_t* store = wasmtime_store_new(engine, NULL, NULL);
  wasmtime_context_t* context = wasmtime_store_context(store);

  // Load binary.
  printf("Loading binary...\n");
  FILE* file = fopen("target/wasm32-unknown-unknown/debug/fib.wasm", "rb");
  if (!file) {
    printf("> Error opening module!\n");
    return 1;
  }
  fseek(file, 0L, SEEK_END);
  size_t file_size = ftell(file);
  fseek(file, 0L, SEEK_SET);
  wasm_byte_vec_t binary;
  wasm_byte_vec_new_uninitialized(&binary, file_size);
  if (fread(binary.data, file_size, 1, file) != 1) {
    printf("> Error reading module!\n");
    return 1;
  }
  fclose(file);

  // Compile.
  printf("Compiling module...\n");
  wasmtime_module_t *module = NULL;
  wasmtime_error_t* error = wasmtime_module_new(engine, (uint8_t*) binary.data, binary.size, &module);
  if (!module)
    exit_with_error("failed to compile module", error, NULL);
  wasm_byte_vec_delete(&binary);

  // Instantiate.
  printf("Instantiating module...\n");
  wasmtime_instance_t instance;
  wasm_trap_t *trap = NULL;
  error = wasmtime_instance_new(context, module, NULL, 0, &instance, &trap);
  if (error != NULL || trap != NULL)
    exit_with_error("failed to instantiate", error, trap);
  wasmtime_module_delete(module);

  // Extract export.
  wasmtime_extern_t fib;
  bool ok = wasmtime_instance_export_get(context, &instance, "fib", 3, &fib);
  assert(ok);

  // Call.
  printf("Calling fib...\n");
  wasmtime_val_t params[1];
  params[0].kind = WASMTIME_I32;
  params[0].of.i32 = 6;
  wasmtime_val_t results[1];
  error = wasmtime_func_call(context, &fib.of.func, params, 1, results, 1, &trap);
  if (error != NULL || trap != NULL)
    exit_with_error("failed to call function", error, trap);

  assert(results[0].kind == WASMTIME_I32);
  printf("> fib(6) = %d\n", results[0].of.i32);

  // Shut down.
  printf("Shutting down...\n");
  wasmtime_store_delete(store);
  wasm_engine_delete(engine);

  // All done.
  printf("Done.\n");
  return 0;
}

static void exit_with_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap) {
  fprintf(stderr, "error: %s\n", message);
  wasm_byte_vec_t error_message;
  if (error != NULL) {
    wasmtime_error_message(error, &error_message);
  } else {
    wasm_trap_message(trap, &error_message);
  }
  fprintf(stderr, "%.*s\n", (int) error_message.size, error_message.data);
  wasm_byte_vec_delete(&error_message);
  exit(1);
}

Using multi-value

You can also browse this source code online and clone the wasmtime repository to run the example locally.

This example shows off how to interact with a wasm module that uses multi-value exports and imports.

multi.wat

(module
  (func $f (import "" "f") (param i32 i64) (result i64 i32))

  (func $g (export "g") (param i32 i64) (result i64 i32)
    (call $f (local.get 0) (local.get 1))
  )

  (func $round_trip_many
    (export "round_trip_many")
    (param i64 i64 i64 i64 i64 i64 i64 i64 i64 i64)
    (result i64 i64 i64 i64 i64 i64 i64 i64 i64 i64)

    local.get 0
    local.get 1
    local.get 2
    local.get 3
    local.get 4
    local.get 5
    local.get 6
    local.get 7
    local.get 8
    local.get 9)
)

multi.c

/*
Example of instantiating of the WebAssembly module and invoking its exported
function.

You can compile and run this example on Linux with:

   cargo build --release -p wasmtime-c-api
   cc examples/multi.c \
       -I crates/c-api/include \
       -I crates/c-api/wasm-c-api/include \
       target/release/libwasmtime.a \
       -lpthread -ldl -lm \
       -o multi
   ./multi

Note that on Windows and macOS the command will be similar, but you'll need
to tweak the `-lpthread` and such annotations.

You can also build using cmake:

mkdir build && cd build && cmake .. && cmake --build . --target wasmtime-multi

Also note that this example was taken from
https://github.com/WebAssembly/wasm-c-api/blob/master/example/multi.c
originally
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <wasm.h>
#include <wasmtime.h>

static void exit_with_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap);

// A function to be called from Wasm code.
wasm_trap_t* callback(
  void *env,
  wasmtime_caller_t *caller,
  const wasmtime_val_t* args,
  size_t nargs,
  wasmtime_val_t* results,
  size_t nresults
) {
  printf("Calling back...\n");
  printf("> %"PRIu32" %"PRIu64"\n", args[0].of.i32, args[1].of.i64);
  printf("\n");

  results[0] = args[1];
  results[1] = args[0];
  return NULL;
}


// A function closure.
wasm_trap_t* closure_callback(
  void* env,
  wasmtime_caller_t *caller,
  const wasmtime_val_t* args,
  size_t nargs,
  wasmtime_val_t* results,
  size_t nresults
) {
  int i = *(int*)env;
  printf("Calling back closure...\n");
  printf("> %d\n", i);

  results[0].kind = WASMTIME_I32;
  results[0].of.i32 = (int32_t)i;
  return NULL;
}


int main(int argc, const char* argv[]) {
  // Initialize.
  printf("Initializing...\n");
  wasm_engine_t* engine = wasm_engine_new();
  wasmtime_store_t* store = wasmtime_store_new(engine, NULL, NULL);
  wasmtime_context_t *context = wasmtime_store_context(store);

  // Load our input file to parse it next
  FILE* file = fopen("examples/multi.wat", "r");
  if (!file) {
    printf("> Error loading file!\n");
    return 1;
  }
  fseek(file, 0L, SEEK_END);
  size_t file_size = ftell(file);
  fseek(file, 0L, SEEK_SET);
  wasm_byte_vec_t wat;
  wasm_byte_vec_new_uninitialized(&wat, file_size);
  if (fread(wat.data, file_size, 1, file) != 1) {
    printf("> Error loading module!\n");
    return 1;
  }
  fclose(file);

  // Parse the wat into the binary wasm format
  wasm_byte_vec_t binary;
  wasmtime_error_t *error = wasmtime_wat2wasm(wat.data, wat.size, &binary);
  if (error != NULL)
    exit_with_error("failed to parse wat", error, NULL);
  wasm_byte_vec_delete(&wat);

  // Compile.
  printf("Compiling module...\n");
  wasmtime_module_t* module = NULL;
  error = wasmtime_module_new(engine, (uint8_t*) binary.data, binary.size, &module);
  if (error)
    exit_with_error("failed to compile module", error, NULL);
  wasm_byte_vec_delete(&binary);

  // Create external print functions.
  printf("Creating callback...\n");
  wasm_functype_t* callback_type = wasm_functype_new_2_2(
      wasm_valtype_new_i32(),
      wasm_valtype_new_i64(),
      wasm_valtype_new_i64(),
      wasm_valtype_new_i32()
  );
  wasmtime_func_t callback_func;
  wasmtime_func_new(context, callback_type, callback, NULL, NULL, &callback_func);
  wasm_functype_delete(callback_type);

  // Instantiate.
  printf("Instantiating module...\n");
  wasmtime_extern_t imports[1];
  imports[0].kind = WASMTIME_EXTERN_FUNC;
  imports[0].of.func = callback_func;
  wasmtime_instance_t instance;
  wasm_trap_t* trap = NULL;
  error = wasmtime_instance_new(context, module, imports, 1, &instance, &trap);
  if (error != NULL || trap != NULL)
    exit_with_error("failed to instantiate", error, trap);
  wasmtime_module_delete(module);

  // Extract export.
  printf("Extracting export...\n");
  wasmtime_extern_t run;
  bool ok = wasmtime_instance_export_get(context, &instance, "g", 1, &run);
  assert(ok);
  assert(run.kind == WASMTIME_EXTERN_FUNC);

  // Call.
  printf("Calling export...\n");
  wasmtime_val_t args[2];
  args[0].kind = WASMTIME_I32;
  args[0].of.i32 = 1;
  args[1].kind = WASMTIME_I64;
  args[1].of.i64 = 2;
  wasmtime_val_t results[2];
  error = wasmtime_func_call(context, &run.of.func, args, 2, results, 2, &trap);
  if (error != NULL || trap != NULL)
    exit_with_error("failed to call run", error, trap);

  // Print result.
  printf("Printing result...\n");
  printf("> %"PRIu64" %"PRIu32"\n",
    results[0].of.i64, results[1].of.i32);

  assert(results[0].kind == WASMTIME_I64);
  assert(results[0].of.i64 == 2);
  assert(results[1].kind == WASMTIME_I32);
  assert(results[1].of.i32 == 1);

  // Shut down.
  printf("Shutting down...\n");
  wasmtime_store_delete(store);
  wasm_engine_delete(engine);

  // All done.
  printf("Done.\n");
  return 0;
}

static void exit_with_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap) {
  fprintf(stderr, "error: %s\n", message);
  wasm_byte_vec_t error_message;
  if (error != NULL) {
    wasmtime_error_message(error, &error_message);
    wasmtime_error_delete(error);
  } else {
    wasm_trap_message(trap, &error_message);
    wasm_trap_delete(trap);
  }
  fprintf(stderr, "%.*s\n", (int) error_message.size, error_message.data);
  wasm_byte_vec_delete(&error_message);
  exit(1);
}

Using WebAssembly from your Language

Wasmtime can be used as a library to embed WebAssembly execution support within applications. Wasmtime is written in Rust, but bindings are available through a C API for a number of other languages too:

Using WebAssembly from Rust

This document shows an example of how to embed Wasmtime using the Rust API to execute a simple wasm program. Be sure to also check out the full API documentation for a full listing of what the wasmtime crate has to offer and the book examples for Rust for more information.

Creating the WebAssembly to execute

Creation of a WebAssembly file is generally covered by the Writing WebAssembly chapter, so we'll just assume that you've already got a wasm file on hand for the rest of this tutorial. To make things simple we'll also just assume you've got a hello.wat file which looks like this:

(module
  (func (export "answer") (result i32)
     i32.const 42
  )
)

Here we're just exporting one function which returns an integer that we'll read from Rust.

Hello, World!

First up let's create a rust project

$ cargo new --bin wasmtime_hello
$ cd wasmtime_hello

Next you'll want to add hello.wat to the root of your project.

We will be using the wasmtime crate to run the wasm file, so next up we need a dependency in Cargo.toml:

[dependencies]
wasmtime = "1.0.0"

Next up let's write the code that we need to execute this wasm file. The simplest version of this looks like so:

extern crate wasmtime;
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();

if false {
    // We start off by creating a `Module` which represents a compiled form
    // of our input wasm module. In this case it'll be JIT-compiled after
    // we parse the text format.
    let module = Module::from_file(&engine, "hello.wat")?;
}
let module = Module::new(&engine, r#"(module (func (export "answer") (result i32) i32.const 42))"#)?;

    // 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(&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(&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>(&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(&mut store, ())?;
    println!("Answer: {:?}", result);
    Ok(())
}

We can build and execute our example with cargo run. Note that by depending on wasmtime you're depending on a JIT compiler, so it may take a moment to build all of its dependencies:

$ cargo run
  Compiling ...
  ...
   Finished dev [unoptimized + debuginfo] target(s) in 42.32s
    Running `wasmtime_hello/target/debug/wasmtime_hello`
Answer: 42

and there we go! We've now executed our first WebAssembly in wasmtime and gotten the result back.

Importing Host Functionality

What we've just seen is a pretty small example of how to call a wasm function and take a look at the result. Most interesting wasm modules, however, are going to import some functions to do something a bit more interesting. For that you'll need to provide imported functions from Rust for wasm to call!

Let's take a look at a wasm module which imports a logging function as well as some simple arithmetic from the environment.

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

This wasm module will call our "log" import a few times and then also call the "double" import. We can compile and instantiate this module with code that looks like this:

extern crate wasmtime;
use std::error::Error;
use wasmtime::*;

struct Log {
    integers_logged: Vec<u32>,
}

fn main() -> Result<(), Box<dyn Error>> {
    let engine = Engine::default();
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))"#)?;

    // 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);
    })?;

    // 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)?;

    // Like before, we can get the run function and execute it.
    let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
    run.call(&mut store, ())?;

    // We can also inspect what integers were logged:
    println!("logged integers: {:?}", store.data().integers_logged);

    Ok(())
}

Note that there's a number of ways to define a Func, be sure to consult its documentation for other ways to create a host-defined function.

C

... more coming soon

In the meantime, have a look at the C API docs.

Using WebAssembly from Python

Wasmtime is available on PyPI and can be used programmatically or as a python module loader, which allows almost any WebAssembly module to be used as a python module. This guide will go over adding Wasmtime to your project, and some provided examples of what can be done with WebAssembly modules.

Make sure you've got Python 3.5 or newer installed locally, and we can get started!

Getting started and simple example

First, copy this example WebAssembly text module into your project. It exports a function for calculating the greatest common denominator of two numbers.

(module
  (func $gcd (param i32 i32) (result i32)
    (local i32)
    block  ;; label = @1
      block  ;; label = @2
        local.get 0
        br_if 0 (;@2;)
        local.get 1
        local.set 2
        br 1 (;@1;)
      end
      loop  ;; label = @2
        local.get 1
        local.get 0
        local.tee 2
        i32.rem_u
        local.set 0
        local.get 2
        local.set 1
        local.get 0
        br_if 0 (;@2;)
      end
    end
    local.get 2
  )
  (export "gcd" (func $gcd))
)

Next, install the Wasmtime package from PyPi. It can be installed as a dependency through Pip or related tools such as Pipenv.

pip install wasmtime

Or

pipenv install wasmtime

After you have Wasmtime installed and you've imported wasmtime, you can import WebAssembly modules in your project like any other python module.

import wasmtime.loader
import gcd

print("gcd(27, 6) =", gcd.gcd(27, 6))

This script should output

gcd(27, 6) = 3

If this is the output you see, congrats! You've successfully ran your first WebAssembly code in python!

You can also alternatively use the wasmtime package's API:

from wasmtime import Store, Module, Instance

store = Store()
module = Module.from_file(store.engine, 'gcd.wat')
instance = Instance(store, module, [])
gcd = instance.exports(store)['gcd']
print("gcd(27, 6) = %d" % gcd(store, 27, 6))

More examples and contributing

The wasmtime Python package currently lives in its own repository outside of wasmtime and has a number of other more advanced examples as well. Feel free to browse those, but if you find anything missing don't hesitate to open an issue and let us know if you have any questions!

Using WebAssembly from .NET

The Wasmtime NuGet package can be used to programmatically interact with WebAssembly modules.

This guide will go over adding Wasmtime to your project and demonstrate a simple example of using a WebAssembly module from C#.

Make sure you have a .NET Core SDK 3.0 SDK or later installed before we get started!

Getting started and simple example

Start by creating a new .NET Core console project:

$ mkdir gcd
$ cd gcd
$ dotnet new console

Next, add a reference to the Wasmtime NuGet package to your project:

$ dotnet add package --version 0.19.0-preview1 wasmtime

Copy this example WebAssembly text module into your project directory as gcd.wat.

(module
  (func $gcd (param i32 i32) (result i32)
    (local i32)
    block  ;; label = @1
      block  ;; label = @2
        local.get 0
        br_if 0 (;@2;)
        local.get 1
        local.set 2
        br 1 (;@1;)
      end
      loop  ;; label = @2
        local.get 1
        local.get 0
        local.tee 2
        i32.rem_u
        local.set 0
        local.get 2
        local.set 1
        local.get 0
        br_if 0 (;@2;)
      end
    end
    local.get 2
  )
  (export "gcd" (func $gcd))
)

This module exports a function for calculating the greatest common denominator of two numbers.

Replace the code in Program.cs with the following:

using System;
using Wasmtime;

namespace Tutorial
{
    class Program
    {
        static void Main(string[] args)
        {
            using var engine = new Engine();
            using var module = Module.FromTextFile(engine, "gcd.wat");

            using var host = new Host(engine);
            using dynamic instance = host.Instantiate(module);

            Console.WriteLine($"gcd(27, 6) = {instance.gcd(27, 6)}");
        }
    }
}

Run the .NET core program:

$ dotnet run

The program should output:

gcd(27, 6) = 3

If this is the output you see, congrats! You've successfully ran your first WebAssembly code in .NET!

More examples and contributing

The .NET embedding of Wasmtime repository contains the source code for the Wasmtime NuGet package.

The repository also has more examples as well.

Feel free to browse those, but if you find anything missing don't hesitate to open an issue and let us know if you have any questions!

Using WebAssembly from Go

Wasmtime is available as a Go Module. This guide will go over adding Wasmtime to your project, and some provided examples of what can be done with WebAssembly modules.

Make sure you're using Go 1.12 or later with modules support.

Getting started and simple example

First up you'll want to start a new module:

$ mkdir hello-wasm
$ cd hello-wasm
$ go mod init hello-wasm
$ go get github.com/bytecodealliance/wasmtime-go

Next, copy this example WebAssembly text module into your project. It exports a function for calculating the greatest common denominator of two numbers.

(module
  (func $gcd (param i32 i32) (result i32)
    (local i32)
    block  ;; label = @1
      block  ;; label = @2
        local.get 0
        br_if 0 (;@2;)
        local.get 1
        local.set 2
        br 1 (;@1;)
      end
      loop  ;; label = @2
        local.get 1
        local.get 0
        local.tee 2
        i32.rem_u
        local.set 0
        local.get 2
        local.set 1
        local.get 0
        br_if 0 (;@2;)
      end
    end
    local.get 2
  )
  (export "gcd" (func $gcd))
)

Next, we can write our code in main.go which reads this file and runs it:

package main

import (
    "fmt"
    "github.com/bytecodealliance/wasmtime-go"
)

func main() {
    engine := wasmtime.NewEngine()
    store := wasmtime.NewStore(engine)
    module, err := wasmtime.NewModuleFromFile(engine, "gcd.wat")
    check(err)
    instance, err := wasmtime.NewInstance(store, module, []wasmtime.AsExtern{})
    check(err)

    gcd := instance.GetExport(store, "gcd").Func()
    val, err := gcd.Call(store, 6, 27)
    check(err)
    fmt.Printf("gcd(6, 27) = %d\n", val.(int32))
}

func check(err error) {
    if err != nil {
        panic(err)
    }
}

And finally we can build and run it:

$ go run main.go
gcd(6, 27) = 3

If this is the output you see, congrats! You've successfully ran your first WebAssembly code in Go!

More examples and contributing

The wasmtime Go package lives in its own repository and has a number of other more advanced examples as well. Feel free to browse those, but if you find anything missing don't hesitate to open an issue and let us know if you have any questions!

Using WebAssembly from Bash

Getting started and simple example

First up you'll want to start a new module:

$ mkdir -p gcd-bash
$ cd gcd-bash
$ touch gcd.wat gcd.sh

Next, copy this example WebAssembly text module into your project. It exports a function for calculating the greatest common denominator of two numbers.

gcd.wat

(module
  (func $gcd (param i32 i32) (result i32)
    (local i32)
    block  ;; label = @1
      block  ;; label = @2
        local.get 0
        br_if 0 (;@2;)
        local.get 1
        local.set 2
        br 1 (;@1;)
      end
      loop  ;; label = @2
        local.get 1
        local.get 0
        local.tee 2
        i32.rem_u
        local.set 0
        local.get 2
        local.set 1
        local.get 0
        br_if 0 (;@2;)
      end
    end
    local.get 2
  )
  (export "gcd" (func $gcd))
)

Create a bash script that will invoke GCD three times.

gcd.sh

#!/bin/bash

function gcd() {
  # Cast to number; default = 0
  local x=$(($1))
  local y=$(($2))
  # Invoke GCD from module; suppress stderr
  local result=$(wasmtime examples/gcd.wat --invoke gcd $x $y 2>/dev/null)
  echo "$result"
}

# main
for num in "27 6" "6 27" "42 12"; do
  set -- $num
  echo "gcd($1, $2) = $(gcd "$1" "$2")"
done

Using the wasmtime CLI

In addition to the embedding API which allows you to use Wasmtime as a library, the Wasmtime project also provides a wasmtime CLI tool to conveniently execute WebAssembly modules from the command line.

This section will provide a guide to the wasmtime CLI and major functionality that it contains. In short, however, you can execute a WebAssembly file (actually doing work as part of the start function) like so:

$ wasmtime foo.wasm

Or similarly if you want to invoke a "start" function, such as with WASI modules, you can execute

$ wasmtime --invoke _start foo.wasm

For more information be sure to check out how to install the CLI, the list of options you can pass, and how to enable logging.

Installing wasmtime

Here we'll show you how to install the wasmtime command line tool. Note that this is distinct from embedding the Wasmtime project into another, for that you'll want to consult the embedding documentation.

The easiest way to install the wasmtime CLI tool is through our installation script. Linux and macOS users can execute the following:

$ curl https://wasmtime.dev/install.sh -sSf | bash

This will download a precompiled version of wasmtime and place it in $HOME/.wasmtime, and update your shell configuration to place the right directory in PATH.

Windows users will want to visit our releases page and can download the MSI installer (wasmtime-dev-x86_64-windows.msi for example) and use that to install.

You can confirm your installation works by executing:

$ wasmtime -V
wasmtime 0.12.0

And now you're off to the races! Be sure to check out the various CLI options as well.

Download Precompiled Binaries

If you'd prefer to not use an installation script, or you're perhaps orchestrating something in CI, you can also download one of our precompiled binaries of wasmtime. We have two channels of releases right now for precompiled binaries:

  1. Each tagged release will have a full set of release artifacts on the GitHub releases page.
  2. The dev release is also continuously updated with the latest build of the main branch. If you want the latest-and-greatest and don't mind a bit of instability, this is the release for you.

When downloading binaries you'll likely want one of the following archives (for the dev release)

  • Linux users - [wasmtime-dev-x86_64-linux.tar.xz]
  • macOS users - [wasmtime-dev-x86_64-macos.tar.xz]
  • Windows users - [wasmtime-dev-x86_64-windows.zip]

Each of these archives has a wasmtime binary placed inside which can be executed normally as the CLI would.

Compiling from Source

If you'd prefer to compile the wasmtime CLI from source, you'll want to consult the contributing documentation for building. Be sure to use a --release build if you're curious to do benchmarking!

CLI Options for wasmtime

The wasmtime CLI is organized into a few subcommands. If no subcommand is provided it'll assume run, which is to execute a wasm file. The subcommands supported by wasmtime are:

help

This is a general subcommand used to print help information to the terminal. You can execute any number of the following:

$ wasmtime help
$ wasmtime --help
$ wasmtime -h
$ wasmtime help run
$ wasmtime run -h

When in doubt, try running the help command to learn more about functionality!

run

This is the wasmtime CLI's main subcommand, and it's also the default if no other subcommand is provided. The run command will execute a WebAssembly module. This means that the module will be compiled to native code, instantiated, and then optionally have an export executed.

The wasmtime CLI will automatically hook up any WASI-related imported functionality, but at this time if your module imports anything else it will fail instantiation.

The run command takes one positional argument which is the name of the module to run:

$ wasmtime run foo.wasm
$ wasmtime foo.wasm

Note that the wasmtime CLI can take both a binary WebAssembly file (*.wasm) as well as the text format for WebAssembly (*.wat):

$ wasmtime foo.wat

The run command accepts an optional invoke argument which is the name of an exported function of the module to run.

$ wasmtime run foo.wasm --invoke initialize

wast

The wast command executes a *.wast file which is the test format for the official WebAssembly spec test suite. This subcommand will execute the script file which has a number of directives supported to instantiate modules, link tests, etc.

Executing this looks like:

$ wasmtime wast foo.wast

config

This subcommand is used to control and edit local Wasmtime configuration settings. The primary purpose of this currently is to configure how Wasmtime's code caching works. You can create a new configuration file for you to edit with:

$ wasmtime config new

And that'll print out the path to the file you can edit.

compile

This subcommand is used to Ahead-Of-Time (AOT) compile a WebAssembly module to produce a "compiled wasm" (.cwasm) file.

The wasmtime run subcommand can then be used to run a AOT-compiled WebAssembly module:

$ wasmtime compile foo.wasm
$ wasmtime foo.cwasm

AOT-compiled modules can be run from hosts that are compatible with the target environment of the AOT-completed module.

settings

This subcommand is used to print the available Cranelift settings for a given target.

When run without options, it will print the settings for the host target and also display what Cranelift settings are inferred for the host:

$ wasmtime settings

Cache Configuration of wasmtime

The configuration file uses the toml format. You can create a configuration file at the default location with:

$ wasmtime config new

It will print the location regardless of the success. Please refer to the --help message for using a custom location.

All settings, except enabled, are optional. If the setting is not specified, the default value is used. Thus, if you don't know what values to use, don't specify them. The default values might be tuned in the future.

Wasmtime assumes all the options are in the cache section.

Example config:

[cache]
enabled = true
directory = "/nfs-share/wasmtime-cache/"
cleanup-interval = "30m"
files-total-size-soft-limit = "1Gi"

Please refer to the cache system section to learn how it works.

If you think some default value should be tuned, some new settings should be introduced or some behavior should be changed, you are welcome to discuss it and contribute to the Wasmtime repository.

Setting enabled

  • type: boolean
  • format: true | false
  • default: true

Specifies whether the cache system is used or not.

This field is mandatory. The default value is used when configuration file is not specified and none exists at the default location.

Setting directory

  • type: string (path)
  • default: look up cache_dir in directories crate

Specifies where the cache directory is. Must be an absolute path.

Setting worker-event-queue-size

  • type: string (SI prefix)
  • format: "{integer}(K | M | G | T | P)?"
  • default: "16"

Size of cache worker event queue. If the queue is full, incoming cache usage events will be dropped.

Setting baseline-compression-level

  • type: integer
  • default: 3, the default zstd compression level

Compression level used when a new cache file is being written by the cache system. Wasmtime uses zstd compression.

Setting optimized-compression-level

  • type: integer
  • default: 20

Compression level used when the cache worker decides to recompress a cache file. Wasmtime uses zstd compression.

Setting optimized-compression-usage-counter-threshold

  • type: string (SI prefix)
  • format: "{integer}(K | M | G | T | P)?"
  • default: "256"

One of the conditions for the cache worker to recompress a cache file is to have usage count of the file exceeding this threshold.

Setting cleanup-interval

  • type: string (duration)
  • format: "{integer}(s | m | h | d)"
  • default: "1h"

When the cache worker is notified about a cache file being updated by the cache system and this interval has already passed since last cleaning up, the worker will attempt a new cleanup.

Please also refer to allowed-clock-drift-for-files-from-future.

Setting optimizing-compression-task-timeout

  • type: string (duration)
  • format: "{integer}(s | m | h | d)"
  • default: "30m"

When the cache worker decides to recompress a cache file, it makes sure that no other worker has started the task for this file within the last optimizing-compression-task-timeout interval. If some worker has started working on it, other workers are skipping this task.

Please also refer to the allowed-clock-drift-for-files-from-future section.

Setting allowed-clock-drift-for-files-from-future

  • type: string (duration)
  • format: "{integer}(s | m | h | d)"
  • default: "1d"

Locks

When the cache worker attempts acquiring a lock for some task, it checks if some other worker has already acquired such a lock. To be fault tolerant and eventually execute every task, the locks expire after some interval. However, because of clock drifts and different timezones, it would happen that some lock was created in the future. This setting defines a tolerance limit for these locks. If the time has been changed in the system (i.e. two years backwards), the cache system should still work properly. Thus, these locks will be treated as expired (assuming the tolerance is not too big).

Cache files

Similarly to the locks, the cache files or their metadata might have modification time in distant future. The cache system tries to keep these files as long as possible. If the limits are not reached, the cache files will not be deleted. Otherwise, they will be treated as the oldest files, so they might survive. If the user actually uses the cache file, the modification time will be updated.

Setting file-count-soft-limit

  • type: string (SI prefix)
  • format: "{integer}(K | M | G | T | P)?"
  • default: "65536"

Soft limit for the file count in the cache directory.

This doesn't include files with metadata. To learn more, please refer to the cache system section.

Setting files-total-size-soft-limit

  • type: string (disk space)
  • format: "{integer}(K | Ki | M | Mi | G | Gi | T | Ti | P | Pi)?"
  • default: "512Mi"

Soft limit for the total size* of files in the cache directory.

This doesn't include files with metadata. To learn more, please refer to the cache system section.

*this is the file size, not the space physically occupied on the disk.

Setting file-count-limit-percent-if-deleting

  • type: string (percent)
  • format: "{integer}%"
  • default: "70%"

If file-count-soft-limit is exceeded and the cache worker performs the cleanup task, then the worker will delete some cache files, so after the task, the file count should not exceed file-count-soft-limit * file-count-limit-percent-if-deleting.

This doesn't include files with metadata. To learn more, please refer to the cache system section.

Setting files-total-size-limit-percent-if-deleting

  • type: string (percent)
  • format: "{integer}%"
  • default: "70%"

If files-total-size-soft-limit is exceeded and cache worker performs the cleanup task, then the worker will delete some cache files, so after the task, the files total size should not exceed files-total-size-soft-limit * files-total-size-limit-percent-if-deleting.

This doesn't include files with metadata. To learn more, please refer to the cache system section.

How does the cache work?

This is an implementation detail and might change in the future. Information provided here is meant to help understanding the big picture and configuring the cache.

There are two main components - the cache system and the cache worker.

Cache system

Handles GET and UPDATE cache requests.

  • GET request - simply loads the cache from disk if it is there.
  • UPDATE request - compresses received data with zstd and baseline-compression-level, then writes the data to the disk.

In case of successful handling of a request, it notifies the cache worker about this event using the queue. The queue has a limited size of worker-event-queue-size. If it is full, it will drop new events until the cache worker pops some event from the queue.

Cache worker

The cache worker runs in a single thread with lower priority and pops events from the queue in a loop handling them one by one.

On GET request

  1. Read the statistics file for the cache file, increase the usage counter and write it back to the disk.

  2. Attempt recompressing the cache file if all of the following conditions are met:

    When recompressing, optimized-compression-level is used as a compression level.

On UPDATE request

  1. Write a fresh statistics file for the cache file.
  2. Clean up the cache if no worker has attempted to do this within the last cleanup-interval. During this task:

Metadata files

  • every cached WebAssembly module has its own statistics file
  • every lock is a file

Writing WebAssembly

Wasmtime is a runtime for executing WebAssembly but you also at some point need to actually produce the WebAssembly module to feed into Wasmtime! This section of the guide is intended to provide some introductory documentation for compiling source code to WebAssembly to later run in Wasmtime. There's plenty of other documentation on the web for doing this, so you'll want to be sure to check out your language's documentation for WebAssembly as well.

Rust

The Rust Programming Language supports WebAssembly as a compilation target. If you're not familiar with Rust it's recommended to start with its introductory documentation. Compiling to WebAssembly will involve specifying the desired target via the --target flag, and to do this there are a number of "target triples" for WebAssembly compilation in Rust:

  • wasm32-wasi - when using wasmtime this is likely what you'll be using. The WASI target is integrated into the standard library and is intended on producing standalone binaries.
  • wasm32-unknown-unknown - this target, like the WASI one, is focused on producing single *.wasm binaries. The standard library, however, is largely stubbed out since the "unknown" part of the target means libstd can't assume anything. This means that while binaries will likely work in wasmtime, common conveniences like println! or panic! won't work.
  • wasm32-unknown-emscripten - this target is intended to work in a web browser and produces a *.wasm file coupled with a *.js file, and it is not compatible with wasmtime.

For the rest of this documentation we'll assume that you're using the wasm32-wasi target for compiling Rust code and executing inside of wasmtime.

Hello, World!

Cross-compiling to WebAssembly involves a number of knobs that need configuration, but you can often gloss over these internal details by using build tooling intended for the WASI target. For example we can start out writing a WebAssembly binary with cargo wasi.

First up we'll install cargo wasi:

$ cargo install cargo-wasi

Next we'll make a new Cargo project:

$ cargo new hello-world
$ cd hello-world

Inside of src/main.rs you'll see the canonical Rust "Hello, World!" using println!. We'll be executing this for the wasm32-wasi target, so you'll want to make sure you're previously built wasmtime and inserted it into PATH;

$ cargo wasi run
info: downloading component 'rust-std' for 'wasm32-wasi'
info: installing component 'rust-std' for 'wasm32-wasi'
   Compiling hello-world v0.1.0 (/hello-world)
    Finished dev [unoptimized + debuginfo] target(s) in 0.16s
     Running `/.cargo/bin/cargo-wasi target/wasm32-wasi/debug/hello-world.wasm`
     Running `target/wasm32-wasi/debug/hello-world.wasm`
Hello, world!

And we're already running our first WebAssembly code inside of wasmtime!

While it's automatically happening for you as part of cargo wasi, you can also run wasmtime yourself:

$ wasmtime target/wasm32-wasi/debug/hello-world.wasm
Hello, world!

You can check out the introductory documentation of cargo-wasi as well for some more information.

Writing Libraries

Previously for "Hello, World!" we created a binary project which used src/main.rs. Not all *.wasm binaries are intended to be executed like commands, though. Some are intended to be loaded into applications and called through various APIs, acting more like libraries. For this use case you'll want to add this to Cargo.toml:

# in Cargo.toml ...

[lib]
crate-type = ['cdylib']

and afterwards you'll want to write your code in src/lib.rs like so:

#![allow(unused)]
fn main() {
#[no_mangle]
pub extern "C" fn print_hello() {
    println!("Hello, world!");
}
}

When you execute cargo wasi build that'll generate a *.wasm file which has one exported function, print_hello. We can then run it via the CLI like so:

$ cargo wasi build
   Compiling hello-world v0.1.0 (/home/alex/code/hello-world)
    Finished dev [unoptimized + debuginfo] target(s) in 0.08s
$ wasmtime --invoke print_hello target/wasm32-wasi/debug/hello_world.wasm
Hello, world!

As a library crate one of your primary consumers may be other languages as well. You'll want to consult the section of this book for using wasmtime from Python and after running through the basics there you can execute our file in Python:

$ cp target/wasm32-wasi/debug/hello_world.wasm .
$ python3
>>> import wasmtime
>>> import hello_world
>>> hello_world.print_hello()
Hello, world!
()
>>>

Note that this form of using #[no_mangle] Rust functions is pretty primitive. You're only able to work with primitive datatypes like integers and floats. While this works for some applications if you need to work with richer types like strings or structs, then you'll want to use the support in wasmtime for interface types.

WebAssembly Interface Types

Note: support for interface types has temporarily removed from Wasmtime. This documentation is somewhat up to date but will no longer work with recent versions of Wasmtime. For more information see https://github.com/bytecodealliance/wasmtime/issues/677

Working with WebAssembly modules at the bare-bones level means that you're only dealing with integers and floats. Many APIs, however, want to work with things like byte arrays, strings, structures, etc. To facilitate these interactions the WebAssembly Interface Types Proposal comes into play. The wasmtime runtime has support for interface types, and the Rust toolchain has library support in a crate called wasm-bindgen.

Note: WebAssembly Interface Types is still a WebAssembly proposal and is under active development. The toolchain may not match the exact specification, and during development you'll generally need to make sure tool versions are all kept up to date to ensure everything aligns right. This'll all smooth over as the proposal stabilizes!

To get started with WebAssembly interface types let's write a library module which will generate a greeting for us. The module itself won't do any printing, we'll simply be working with some strings.

To get starts let's add this to our Cargo.toml:

[lib]
crate-type = ['cdylib']

[dependencies]
wasm-bindgen = "0.2.54"

Using this crate, we can then update our src/lib.rs with the following:

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

Then we can build this with:

$ cargo wasi build --release
    Updating crates.io index
...
    Finished dev [unoptimized + debuginfo] target(s) in 9.57s
 Downloading precompiled wasm-bindgen v0.2.54

and we have our new wasm binary!

Note: for now when using wasm-bindgen you must use --release mode to build wasi binaries with interface types.

We can then test out support for this with the CLI:

$ wasmtime --invoke greet ./target/wasm32-wasi/release/hello_world.wasm "Wasmtime CLI"
warning: using `--invoke` with a function that takes arguments is experimental and may break in the future
warning: using `--invoke` with a function that returns values is experimental and may break in the future
Hello, Wasmtime CLI!

Here we can see some experimental warnings, but we got our error message printed out! The first CLI parameter, "Wasmtime CLI", was passed as the first argument of the greet function. The resulting string was then printed out to the console.

Like before, we can also execute this with Python:

$ cp target/wasm32-wasi/release/hello_world.wasm .
$ python3
>>> import wasmtime
>>> import hello_world
>>> hello_world.greet('python interpreter')
'Hello, python interpreter!'
>>>

Note that wasm-bindgen was originally developed for JS and usage in a browser, but a subset of its implementation (such as arguments which are strings) are supported for WebAssembly interface types. You can also check out the reference documentation for wasm-bindgen for more information about how it works. Note that the wasm-bindgen support for wasm interface type is still in its nascent phase and is likely to be greatly improved in the future.

Exporting Rust functionality

Currently only Rust functions can be exported from a wasm module. Rust functions must be #[no_mangle] to show up in the final binary, but if you're using #[wasm_bindgen] that will happen automatically for you.

Memory is by default exported from Rust modules under the name memory. This can be tweaked with the -Clink-arg flag to rustc to pass flags to LLD, the WebAssembly code linker.

Tables cannot be imported at this time. When using rustc directly there is no support for anyref and only one function table is supported. When using wasm-bindgen it may inject an anyref table if necessary, but this table is an internal detail and is not exported. The function table can be exported by passing the --export-table argument to LLD (via -C link-arg) or can be imported with the --import-table.

Rust currently does not have support for exporting or importing custom global values.

Importing host functionality

Only functions can be imported in Rust at this time, and they can be imported via raw interfaces like:

#![allow(unused)]
fn main() {
struct MyStruct;
#[link(wasm_import_module = "the-wasm-import-module")]
extern "C" {
    // imports the name `foo` from `the-wasm-import-module`
    fn foo();

    // functions can have integer/float arguments/return values
    fn translate(a: i32) -> f32;

    // Note that the ABI of Rust and wasm is somewhat in flux, so while this
    // works, it's recommended to rely on raw integer/float values where
    // possible.
    fn translate_fancy(my_struct: MyStruct) -> u32;

    // you can also explicitly specify the name to import, this imports `bar`
    // instead of `baz` from `the-wasm-import-module`.
    #[link_name = "bar"]
    fn baz();
}
}

When you're using wasm-bindgen you would instead use:

use wasm_bindgen::prelude::*;

#[wasm_bindgen(module = "the-wasm-import-module")]
extern "C" {
    fn foo();
    fn baz();
    // ...
}

Note that unless you're using interface types you likely don't need wasm-bindgen.

C/C++

All the parts needed to support wasm are included in upstream clang, lld, and compiler-rt, as of the LLVM 8.0 release. However, to use it, you'll need to build WebAssembly-targeted versions of the library parts, and it can be tricky to get all the CMake invocations lined up properly.

To make things easier, we provide prebuilt packages that provide builds of Clang and sysroot libraries.

WASI doesn't yet support setjmp/longjmp or C++ exceptions, as it is waiting for unwinding support in WebAssembly.

By default, the C/C++ toolchain orders linear memory to put the globals first, the stack second, and start the heap after that. This reduces code size, because references to globals can use small offsets. However, it also means that stack overflow will often lead to corrupted globals. The -Wl,--stack-first flag to clang instructs it to put the stack first, followed by the globals and the heap, which may produce slightly larger code, but will more reliably trap on stack overflow.

See the wasm-ld documentation for more information and additional flags. Note flags related to dynamic linking, such -shared and --export-dynamic are not yet stable and are expected to change behavior in the future.

AssemblyScript

AssemblyScript has included support for targeting WASI since version 0.10.0. To use it, add import "wasi" at the top of your entrypoint file.

Let's walk through a simple hello world example using the latest AssemblyScript runtime (at the time of this writing, it is AssemblyScript runtime included in version 0.19.x):

wasi-hello-world.ts

import "wasi"
// Import the WASI environment and additional WASI-enabled APIs
console.log('Hello World!\n');
// Call AssemblyScript console.log which in turn calls WASI

This uses as-wasi as a dependency to make working with the AssemblyScript WASI bindings easier. Then, you can run:

asc wasi-hello-world.ts -b wasi-hello-world.wasm

to compile it to wasm, and

wasmtime wasi-hello-world.wasm

to run it from the command-line. Or you can instantiate it using the Wasmtime API.

package.json

It can also be packaged using a package.json file:

{
  "name": "wasi-hello-world",
  "version": "1.0.0",
  "description": "Hello world in Wasi with AS and as-wasi",
  "main": "index.js",
  "scripts": {
    "build": "asc wasi-hello-world.ts --target helloworld",
    "wasmtime": "wasmtime ./build/wasi-hello-world.wasm"
  },
  "author": "Aaron Turner",
  "contributors": [
    "Jairus Tanaka (JairusSW)"
  ],
  "license": "MIT",
  "devDependencies": {
    "assemblyscript": "^0.20.7"
  },
  "dependencies": {}
}

You can also browse this source code online and clone the wasmtime repository to run the example locally.

WebAssembly Text Format (*.wat)

While not necessarily a full-blown language you might be curious how Wasmtime interacts with the *.wat text format! The wasmtime CLI and Rust embedding API both support the *.wat text format by default.

"Hello, World!" is pretty nontrivial in the *.wat format since it's assembly-like and not really intended to be a primary programming language. That being said we can create a simple add function to call it!

For example if you have a file add.wat like so:

(module
  (func (export "add") (param i32 i32) (result i32)
    local.get 0
    local.get 1
    i32.add))

Then you can execute this on the CLI with:

$ wasmtime add.wat --invoke add 1 2
warning: ...
warning: ...
3

And we can see that we're already adding numbers!

You can also see how this works in the Rust API like so:

extern crate wasmtime;
extern crate anyhow;
use wasmtime::*;

fn main() -> anyhow::Result<()> {
let mut store = Store::<()>::default();
let wat = r#"
  (module
    (func (export "add") (param i32 i32) (result i32)
      local.get 0
      local.get 1
      i32.add))
"#;
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(())
}

Stability

... more coming soon

Release Process

Wasmtime's release process was originally designed in an RFC and this page is intended to serve as documentation for the current process as-is today. The high-level summary of Wasmtime's release process is:

  • A new major version of Wasmtime will be made available once a month.
  • Security bugs and correctness fixes will be backported to the latest two releases of Wasmtime and issued as patch releases.

Once a month Wasmtime will issue a new major version. This will be issued with a semver-major version update, such as 4.0.0 to 5.0.0. The precise schedule of Wasmtime's release is currently an automated PR is sent to bump the version on the 5th of every month and a release is made when the PR is merged. The PR typically gets merged within a few days.

Each major release of Wasmtime reserves the right to break both behavior and API backwards-compatibility. This is not expected to happen frequently, however, and any breaking change will follow these criteria:

  • Minor breaking changes, either behavior or with APIs, will be documented in the RELEASES.md release notes. Minor changes will require some degree of consensus but are not required to go through the entire RFC process.

  • Major breaking changes, such as major refactorings to the API, will be required to go through the RFC process. These changes are intended to be broadly communicated to those interested and provides an opportunity to give feedback about embeddings. Release notes will clearly indicate if any major breaking changes through accepted RFCs are included in a release.

Patch releases of Wasmtime will only be issued for security and critical correctness issues for on-by-default behavior in the previous releases. If Wasmtime is currently at version 5.0.0 then 5.0.1 and 4.0.1 will be issued as patch releases if a bug is found. Patch releases are guaranteed to maintain API and behavior backwards-compatibility and are intended to be trivial for users to upgrade to.

Patch releases for Cranelift will be made for any miscompilations found by Cranelift, even those that Wasmtime itself may not exercise. Due to the current release process a patch release for Cranelift will issue a patch release for Wasmtime as well.

What's released?

At this time the release process of Wasmtime encompasses:

  • The wasmtime Rust crate
  • The C API of Wasmtime
  • The wasmtime CLI tool through the wasmtime-cli Rust crate

Other projects maintained by the Bytecode Alliance will also likely be released, with the same version numbers, with the main Wasmtime project soon after a release is made, such as:

Note, though, that bugs and security issues in these projects do not at this time warrant patch releases for Wasmtime.

Tiers of Support in Wasmtime

Wasmtime's support for platforms and features can be distinguished with three different tiers of support. The description of these tiers are intended to be inspired by the Rust compiler's support tiers for targets but are additionally tailored for Wasmtime. Wasmtime's tiered support additionally applies not only to platforms/targets themselves but additionally features implemented within Wasmtime itself.

The purpose of this document is to provide a means by which to evaluate the inclusion of new features and support for existing features within Wasmtime. This should not be used to "lawyer" a change into Wasmtime on a precise technical detail or similar since this document is itself not 100% precise and will change over time.

Current Tier Status

For explanations of what each tier means see below.

Tier 1

CategoryDescription
Targetx86_64-apple-darwin
Targetx86_64-pc-windows-msvc
Targetx86_64-unknown-linux-gnu
WASI Proposalwasi_snapshot_preview1
WASI Proposalwasi_unstable
WebAssembly Proposalbulk-memory
WebAssembly Proposalreference-types
WebAssembly Proposalsimd

Tier 2

CategoryDescriptionMissing Tier 1 Requirements
Targetaarch64-unknown-linux-gnuContinuous fuzzing
Targets390x-unknown-linux-gnuContinuous fuzzing
Targetx86_64-pc-windows-gnuClear owner of the target
WebAssembly Proposalmemory64Unstable wasm proposal
WebAssembly Proposalmulti-memoryUnstable wasm proposal

Tier 3

CategoryDescriptionMissing Tier 2 Requirements
Targetaarch64-apple-darwinCI testing
Targetaarch64-pc-windows-msvcCI testing, unwinding, full-time maintainer
Targetriscv64gc-unknown-linux-gnufull-time maintainer
WASI Proposalwasi-nnMore expansive CI testing
WASI Proposalwasi-cryptoCI testing, clear owner
WebAssembly ProposalthreadsComplete implementation
WebAssembly Proposalcomponent-modelComplete implementation
miscNon-Wasmtime Cranelift usage 1CI testing, full-time maintainer
miscDWARF debugging 2CI testing, full-time maintainer, improved quality
1

This is intended to encompass features that Cranelift supports as a general-purpose code generator such as integer value types other than i32 and i64, non-Wasmtime calling conventions, code model settings, relocation restrictions, etc. These features aren't covered by Wasmtime's usage of Cranelift because the WebAssembly instruction set doesn't leverage them. This means that they receive far less testing and fuzzing than the parts of Cranelift exercised by Wasmtime.

2

Currently there is no active maintainer of DWARF debugging support and support is currently best-effort. Additionally there are known shortcomings and bugs. At this time there's no developer time to improve the situation here as well.

Tier Details

Wasmtime's precise definitions of tiers are not guaranteed to be constant over time, so these descriptions are likely to change over time. Tier 1 is classified as the highest level of support, confidence, and correctness for a component. Each tier additionally encompasses all the guarantees of previous tiers.

Features classified under a particular tier may already meet the criteria for later tiers as well. In situations like this it's not intended to use these guidelines to justify removal of a feature at any one point in time. Guidance is provided here for phasing out unmaintained features but it should be clear under what circumstances work "can be avoided" for each tier.

Tier 3 - Not Production Ready

The general idea behind Tier 3 is that this is the baseline for inclusion of code into the Wasmtime project. This is not intended to be a catch-all "if a patch is sent it will be merged" tier. Instead the goal of this tier is to outline what is expected of contributors adding new features to Wasmtime which might be experimental at the time of addition. This is intentionally not a relaxed tier of restrictions but already implies a significant commitment of effort to a feature being included within Wasmtime.

Tier 3 features include:

  • Inclusion of a feature does not impose unnecessary maintenance overhead on other components/features. Some examples of additions to Wasmtime which would not be accepted are:

    • An experimental feature doubles the time of CI for all PRs.
    • A change which makes it significantly more difficult to architecturally change Wasmtime's internal implementation.
    • A change which makes building Wasmtime more difficult for unrelated developers.

    In general Tier 3 features are off-by-default at compile time but still tested-by-default on CI.

  • New features of Wasmtime cannot have major known bugs at the time of inclusion. Landing a feature in Wasmtime requires the feature to be correct and bug-free as best can be evaluated at the time of inclusion. Inevitably bugs will be found and that's ok, but anything identified during review must be addressed.

  • Code included into the Wasmtime project must be of an acceptable level of quality relative to the rest of the code in Wasmtime.

  • There must be a path to a feature being finished at the time of inclusion. Adding a new backend to Cranelift for example is a significant undertaking which may not be able to be done in a single PR. Partial implementations of a feature are acceptable so long as there's a clear path forward and schedule for completing the feature.

  • New components in Wasmtime must have a clearly identified owner who is willing to be "on the hook" for review, updates to the internals of Wasmtime, etc. For example a new backend in Cranelift would need to have a maintainer who is willing to respond to changes in Cranelift's interfaces and the needs of Wasmtime.

This baseline level of support notably does not require any degree of testing, fuzzing, or verification. As a result components classified as Tier 3 are generally not production-ready as they have not been battle-tested much.

Features classified as Tier 3 may be disabled in CI or removed from the repository as well. If a Tier 3 feature is preventing development of other features then the owner will be notified. If no response is heard from within a week then the feature will be disabled in CI. If no further response happens for a month then the feature may be removed from the repository.

Tier 2 - Almost Production Ready

This tier is meant to encompass features and components of Wasmtime which are well-maintained, tested well, but don't necessarily meet the stringent criteria for Tier 1. Features in this category may already be "production ready" and safe to use.

Tier 2 features include:

  • Tests are run in CI for the Wasmtime project for this feature and everything passes. For example a Tier 2 platform runs in CI directly or via emulation. Features are otherwise fully tested on CI.

  • Complete implementations for anything that's part of Tier 1. For example all Tier 2 targets must implement all of the Tier 1 WebAssembly proposals, and all Tier 2 features must be implemented on all Tier 1 targets.

  • All existing developers are expected to handle minor changes which affect Tier 2 components. For example if Cranelift's interfaces change then the developer changing the interface is expected to handle the changes for Tier 2 architectures so long as the affected part is relatively minor. Note that if a more substantial change is required to a Tier 2 component then that falls under the next bullet.

  • Maintainers of a Tier 2 feature are responsive (reply to requests within a week) and are available to accommodate architectural changes that affect their component. For example more expansive work beyond the previous bullet where contributors can't easily handle changes are expected to be guided or otherwise implemented by Tier 2 maintainers.

  • Major changes otherwise requiring an RFC that affect Tier 2 components are required to consult Tier 2 maintainers in the course of the RFC. Major changes to Tier 2 components themselves do not require an RFC, however.

Features at this tier generally are not turned off or disabled for very long. Maintainers are already required to be responsive to changes and will be notified of any unrelated change which affects their component. It's recommended that if a component breaks for one reason or another due to an unrelated change that the maintainer either contributes to the PR-in-progress or otherwise has a schedule for the implementation of the feature.

Tier 1 - Production Ready

This tier is intended to be the highest level of support in Wasmtime for any particular feature, indicating that it is suitable for production environments. This conveys a high level of confidence in the Wasmtime project about the specified features.

Tier 1 features include:

  • Continuous fuzzing is required for WebAssembly proposals. This means that any WebAssembly proposal must have support in the wasm-smith crate and existing fuzz targets must be running and exercising the new code paths. Where possible differential fuzzing should also be implemented to compare results with other implementations.

  • Continuous fuzzing is required for the architecture of supported targets. For example currently there are three x86_64 targets that are considered Tier 1 but only x86_64-unknown-linux-gnu is fuzzed.

  • CVEs and security releases will be performed as necessary for any bugs found in features and targets.

  • Major changes affecting this component may require help from maintainers with specialized expertise, but otherwise it should be reasonable to expect most Wasmtime developers to be able to maintain Tier 1 features.

  • Major changes affecting Tier 1 features require an RFC and prior agreement on the change before an implementation is committed.

A major inclusion point for this tier is intended to be the continuous fuzzing of Wasmtime. This implies a significant commitment of resources for fixing issues, hardware to execute Wasmtime, etc. Additionally this tier comes with the broadest expectation of "burden on everyone else" in terms of what changes everyone is generally expected to handle.

Features classified as Tier 1 are rarely, if ever, turned off or removed from Wasmtime.

Platform Support

The wasmtime project is a configurable and lightweight runtime for WebAssembly which has a number of ways it can be configured. Not all features are supported on all platforms, but it is intended that wasmtime can run in some capacity on almost all platforms! The matrix of what's being tested, what works, and what's supported where is evolving over time, and this document hopes to capture a snapshot of what the current state of the world looks like.

All features of wasmtime should work on the following platforms:

  • Linux x86_64
  • Linux aarch64
  • macOS x86_64
  • Windows x86_64

For more detailed information about supported platforms, please check out the sections below!

JIT compiler support

The JIT compiler, backed by Cranelift, supports the x86_64 and aarch64 architectures at this time. Support for at least ARM and x86 is planned as well.

Usage of the JIT compiler will require a host operating system which supports creating executable memory pages on-the-fly. In Rust terms this generally means that std needs to be supported on this platform.

Interpreter support

At this time wasmtime does not have a mode in which it simply interprets WebAssembly code. It is planned to add support for an interpreter, however, and this will have minimal system dependencies. It is planned that the system will need to support some form of dynamic memory allocation, but other than that not much else will be needed.

What about #[no_std]?

The wasmtime project does not currently use #[no_std] for its crates, but this is not because it won't support it! At this time we're still gathering use cases for for what #[no_std] might entail, so if you're interested in this we'd love to hear about your use case! Feel free to open an issue on the wasmtime repository to discuss this.

This is a common question we are asked, however, so to provide some more context on why Wasmtime is the way it is, here's some responses to frequent points raised about #![no_std]:

  • What if my platform doesn't have std? - For platforms without support for the Rust standard library the JIT compiler of Wasmtime often won't run on the platform as well. The JIT compiler requires mmap (or an equivalent), and presence of mmap often implies presence of a libc which means Rust's std library works.

    Cargo's -Z build-std feature feature is also intended to help easily build the standard library for all platforms. With this feature you can recompile the standard library (using Nightly Rust for now) with a custom target specification if necessary. Additionally the intention at this time is to get std building for all platforms, regardless of what the platform actually supports. This change is taking time to implement, but rust-lang/rust#74033 is an example of this support growing over time.

    We're also interested in running Wasmtime without a JIT compiler in the future, but that is not implemented at this time. Implementing this will require a lot more work than tagging crates #![no_std]. The Wasmtime developers are also very interested in supporting as many targets as possible, so if Wasmtime doesn't work on your platform yet we'd love to learn why and what we can do to support that platform, but the conversation here is typically more nuanced than simply making wasmtime compile without std.

  • Doesn't #![no_std] have smaller binary sizes? - There's a lot of factors that affect binary size in Rust. Compilation options are a huge one but beyond that idioms and libraries linked matter quite a lot as well. Code is not inherently large when using std instead of core, it's just that often code using std has more dependencies (like std::thread) which requires code to bind. Code size improvements can be made to code using std and core equally, and switching to #![no_std] is not a silver bullet for compile sizes.

  • The patch to switch to #![no_std] is small, why not accept it? - PRs to switch to #![no_std] are often relatively small or don't impact too many parts of the system. There's a lot more to developing a #![no_std] WebAssembly runtime than switching a few crates, however. Maintaining a #![no_std] library over time has a number of costs associated with it:

    • Rust has no stable way to diagnose no_std errors in an otherwise std build, which means that to support this feature it must be tested on CI with a no_std target. This is costly in terms of CI time, CI maintenance, and developers having to do extra builds to avoid CI errors. Note that this isn't more costly than any other platform supported by Wasmtime, but it's a cost nonetheless.

    • Idioms in #![no_std] are quite different than normal Rust code. You'll import from different crates (core instead of std) and data structures have to all be manually imported from alloc. These idioms are difficult to learn for newcomers to the project and are not well documented in the ecosystem. This cost of development and maintenance is not unique to Wasmtime but in general affects the #![no_std] ecosystem at large, unfortunately.

    • Currently Wasmtime does not have a target use case which requires #![no_std] support, so it's hard to justify these costs of development. We're very interested in supporting as many use cases and targets as possible, but the decision to support a target needs to take into account the costs associated so we can plan accordingly. Effectively we need to have a goal in mind instead of taking on the costs of #![no_std] blindly.

    • At this time it's not clear whether #![no_std] will be needed long-term, so eating short-term costs may not pay off in the long run. Features like Cargo's -Z build-std may mean that #![no_std] is less and less necessary over time.

  • How can Wasmtime support #![no_std] if it uses X? - Wasmtime as-is today is not suitable for many #![no_std] contexts. For example it might use mmap for allocating JIT code memory, leverage threads for caching, or use thread locals when calling into JIT code. These features are difficult to support in their full fidelity on all platforms, but the Wasmtime developers are very much aware of this! Wasmtime is intended to be configurable where many of these features are compile-time or runtime options. For example caches can be disabled, JITs can be removed and replaced with interpreters, or users could provide a callback to allocate memory instead of using the OS. This is sort of a long-winded way of saying that Wasmtime on the surface may today look like it won't support #![no_std], but this is almost always simply a matter of time and development priorities rather than a fundamental reason why Wasmtime couldn't support #![no_std].

Note that at this time these guidelines apply not only to Wasmtime but also to some of its dependencies developed by the Bytecode Alliance such as the wasm-tools repository. These projects don't have the same runtime requirements as Wasmtime (e.g. wasmparser doesn't need mmap), but we're following the same guidelines above at this time. Patches to add #![no_std], while possibly small, incur many of the same costs and also have an unclear longevity as features like -Z build-std evolve.

WebAssembly Proposals Support

The following table summarizes Wasmtime's support for WebAssembly proposals as well as the command line flag and wasmtime::Config method you can use to enable or disable support for a proposal.

If a proposal is not listed, then it is not supported by Wasmtime.

Wasmtime will never enable a proposal by default unless it has reached phase 4 of the WebAssembly standardizations process and its implementation in Wasmtime has been thoroughly vetted.

WebAssembly ProposalSupported in Wasmtime?Command Line NameConfig Method
Import and Export Mutable GlobalsYes.
Always enabled.
(none)(none)
Sign-Extension OperationsYes.
Always enabled.
(none)(none)
Non-Trapping Float-to-Int ConversionsYes.
Always enabled.
(none)(none)
Multi-ValueYes.
Enabled by default.
multi-valuewasm_multi_value
Bulk Memory OperationsYes.
Enabled by default.
bulk-memorywasm_bulk_memory
Reference TypesYes.
Enabled by default.
reference-typeswasm_reference_types
Fixed-Width SIMDYes.
Enabled by default.
simdwasm_simd
Threads and AtomicsIn progress.threadswasm_threads
Multi-MemoryYes.multi-memorywasm_multi_memory
Component ModelIn progress.component-modelwasm_component_model
Memory64Yes.memory64wasm_memory64

The "Command Line Name" refers to the --wasm-features CLI argument of the wasmtime executable and the name which must be passed to enable it.

Security

One of WebAssembly (and Wasmtime's) main goals is to execute untrusted code in a safe manner inside of a sandbox. WebAssembly is inherently sandboxed by design (must import all functionality, etc). This document is intended to cover the various sandboxing implementation strategies that Wasmtime has as they are developed.

At this time Wasmtime implements what's necessary for the WebAssembly specification, for example memory isolation between instances. Additionally the safe Rust API is intended to mitigate accidental bugs in hosts.

Different sandboxing implementation techniques will also come with different tradeoffs in terms of performance and feature limitations, and Wasmtime plans to offer users choices of which tradeoffs they want to make.

WebAssembly Core

The core WebAssembly spec has several features which create a unique sandboxed environment:

  • The callstack is inaccessible. Unlike most native execution environments, return addresses from calls and spilled registers are not stored in memory accessible to applications. They are stored in memory that only the implementation has access to, which makes traditional stack-smashing attacks targeting return addresses impossible.

  • Pointers, in source languages which have them, are compiled to offsets into linear memory, so implementations details such as virtual addresses are hidden from applications. And all accesses within linear memory are checked to ensure they stay in bounds.

  • All control transfers—direct and indirect branches, as well as direct and indirect calls—are to known and type-checked destinations, so it's not possible to accidentally call into the middle of a function or branch outside of a function.

  • All interaction with the outside world is done through imports and exports. There is no raw access to system calls or other forms of I/O; the only thing a WebAssembly instance can do is what is available through interfaces it has been explicitly linked with.

  • There is no undefined behavior. Even where the WebAssembly spec permits multiple possible behaviors, it doesn't permit arbitrary behavior.

Defense-in-depth

While WebAssembly is designed to be sandboxed bugs or issues inevitably arise so Wasmtime also implements a number of mitigations which are not required for correct execution of WebAssembly but can help mitigate issues if bugs are found:

  • Linear memories by default are preceded with a 2GB guard region. WebAssembly has no means of ever accessing this memory but this can protect against accidental sign-extension bugs in Cranelift where if an offset is accidentally interpreted as a signed 32-bit offset instead of an unsigned offset it could access memory before the addressable memory for WebAssembly.

  • Wasmtime uses explicit checks to determine if a WebAssembly function should be considered to stack overflow, but it still uses guard pages on all native thread stacks. These guard pages are never intended to be hit and will abort the program if they're hit. Hitting a guard page within WebAssembly indicates a bug in host configuration or a bug in Cranelift itself.

  • Where it can Wasmtime will zero memory used by a WebAssembly instance after it's finished. This is not necessary unless the memory is actually reused for instantiation elsewhere but this is done to prevent accidental leakage of information between instances in the face of other bugs. This applies to linear memories, tables, and the memory used to store instance information itself.

  • The choice of implementation language, Rust, for Wasmtime is also a defense in protecting the authors for Wasmtime from themselves in addition to protecting embedders from themselves. Rust helps catch mistakes when writing Wasmtime itself at compile time. Rust additionally enables Wasmtime developers to create an API that means that embedders can't get it wrong. For example it's guaranteed that Wasmtime won't segfault when using its public API, empowering embedders with confidence that even if the embedding has bugs all of the security guarantees of WebAssembly are still upheld.

  • Wasmtime is in the process of implementing control-flow-integrity mechanisms to leverage hardware state for futher guaranteeing that WebAssembly stays within its sandbox. In the event of a bug in Cranelift this can help mitigate the impact of where control flow can go to.

Filesystem Access

Wasmtime implements the WASI APIs for filesystem access, which follow a capability-based security model, which ensures that applications can only access files and directories they've been given access to. WASI's security model keeps users safe today, and also helps us prepare for shared-nothing linking and nanoprocesses in the future.

Wasmtime developers are intimately engaged with the WASI standards process, libraries, and tooling development, all along the way too.

Terminal Output

If untrusted code is allowed to print text which is displayed to a terminal, it may emit ANSI-style escape sequences and other control sequences which, depending on the terminal the user is using and how it is configured, can have side effects including writing to files, executing commands, injecting text into the stream as if the user had typed it, or reading the output of previous commands. ANSI-style escape sequences can also confuse or mislead users, making other vulnerabilities easier to exploit.

Our first priority is to protect users, so Wasmtime now filters writes to output streams when they are connected to a terminal to translate escape sequences into inert replacement sequences.

Some applications need ANSI-style escape sequences, such as terminal-based editors and programs that use colors, so we are also developing a proposal for the WASI Subgroup for safe and portable ANSI-style escape sequence support, which we hope to post more about soon.

Spectre

Wasmtime implements a few forms of basic spectre mitigations at this time:

  • Bounds checks when accessing entries in a function table (e.g. the call_indirect instruction) are mitigated.

  • The br_table instruction is mitigated to ensure that speculation goes to a deterministic location.

  • Wasmtime's default configuration for linear memory means that bounds checks will not be present for memory accesses due to the reliance on page faults to instead detect out-of-bounds accesses. When Wasmtime is configured with "dynamic" memories, however, Cranelift will insert spectre mitigation for the bounds checks performed for all memory accesses.

Mitigating Spectre continues to be a subject of ongoing research, and Wasmtime will likely grow more mitigations in the future as well.

Disclosure Policy

The disclosure policy for security issues in Wasmtime is documented on the Bytecode Alliance website.

Contributing

We're excited to work on Wasmtime and/or Cranelift together with you! This guide should help you get up and running with Wasmtime and Cranelift development. But first, make sure you've read the Code of Conduct!

Wasmtime and Cranelift are very ambitious projects with many goals, and while we're confident we can achieve some of them, we see many opportunities for people to get involved and help us achieve even more.

Join Our Chat

We chat about Wasmtime and Cranelift development on Zulip — join us!. You can also join specific streams:

If you're having trouble building Wasmtime or Cranelift, aren't sure why a test is failing, or have any other questions, feel free to ask on Zulip. Not everything we hope to do with these projects is reflected in the code or documentation yet, so if you see things that seem missing or that don't make sense, or even that just don't work the way you expect them to, we're also interested to hear about that!

As always, you're more than welcome to open an issue too!

Finally, we have biweekly project meetings, hosted on Zoom, for Wasmtime and Cranelift. For more information, see our meetings agendas/minutes repository. Please feel free to contact us via Zulip if you're interested in joining!

Finding Something to Hack On

If you're looking for something to do, these are great places to start:

  • Issues labeled "good first issue" — these issues tend to be simple, what needs to be done is well known, and are good for new contributors to tackle. The goal is to learn Wasmtime's development workflow and make sure that you can build and test Wasmtime.

  • Issues labeled "help wanted" — these are issues that we need a little help with!

If you're unsure if an issue is a good fit for you or not, feel free to ask in a comment on the issue, or in chat.

Mentoring

We're happy to mentor people, whether you're learning Rust, learning about compiler backends, learning about machine code, learning about wasm, learning about how Cranelift does things, or all together at once.

We categorize issues in the issue tracker using a tag scheme inspired by Rust's issue tags. For example, the E-easy marks good beginner issues, and E-rust marks issues which likely require some familiarity with Rust, though not necessarily Cranelift-specific or even compiler-specific experience. E-compiler-easy marks issues good for beginners who have some familiarity with compilers, or are interested in gaining some :-).

See also the full list of Cranelift labels.

Also, we encourage people to just look around and find things they're interested in. This a good time to get involved, as there aren't a lot of things set in stone yet.

Architecture of Wasmtime

This document is intended to give an overview of the implementation of Wasmtime. This will explain the purposes of the various wasmtime-* crates that the main wasmtime crate depends on. For even more detailed information it's recommended to review the code itself and find the comments contained within.

The wasmtime crate

The main entry point for Wasmtime is the wasmtime crate itself. Wasmtime is designed such that the wasmtime crate is nearly a 100% safe API (safe in the Rust sense) modulo some small and well-documented functions as to why they're unsafe. The wasmtime crate provides features and access to WebAssembly primitives and functionality, such as compiling modules, instantiating them, calling functions, etc.

At this time the wasmtime crate is the first crate that is intended to be consumed by users. First in this sense means that everything wasmtime depends on is thought of as an internal dependency. We publish crates to crates.io but put very little effort into having a "nice" API for internal crates or worrying about breakage between versions of internal crates. This primarily means that all the other crates discussed here are considered internal dependencies of Wasmtime and don't show up in the public API of Wasmtime at all. To use some Cargo terminology, all the wasmtime-* crates that wasmtime depends on are "private" dependencies.

Additionally at this time the safe/unsafe boundary between Wasmtime's internal crates is not the most well-defined. There are methods that should be marked unsafe which aren't, and unsafe methods do not have exhaustive documentation as to why they are unsafe. This is an ongoing matter of improvement, however, where the goal is to have safe methods be actually safe in the Rust sense, as well as having documentation for unsafe methods which clearly lists why they are unsafe.

Important concepts

To preface discussion of more nitty-gritty internals, it's important to have a few concepts in the back of your head. These are some of the important types and their implications in Wasmtime:

  • wasmtime::Engine - this is a global compilation context which is sort of the "root context". An Engine is typically created once per program and is expected to be shared across many threads (internally it's atomically reference counted). Each Engine stores configuration values and other cross-thread data such as type interning for Module instances. The main thing to remember for Engine is that any mutation of its internals typically involves acquiring a lock, whereas for Store below no locks are necessary.

  • wasmtime::Store - this is the concept of a "store" in WebAssembly. While there's also a formal definition to go of this it can be thought of as a bag of related WebAssembly objects. This includes instances, globals, memories, tables, etc. A Store does not implement any form of garbage collection of the internal items (there is a gc function but that's just for externref values). This means that once you create an Instance or a Table the memory is not actually released until the Store itself is deallocated. A Store is sort of a "context" used for almost all wasm operations. Store also contains instance handles which recursively refer back to the Store, leading to a good bit of aliasing of pointers within the Store. The important thing for now, though, is to know that Store is a unit of isolation. WebAssembly objects are always entirely contained within a Store, and at this time nothing can cross between stores (except scalars if you manually hook it up). In other words, wasm objects from different stores cannot interact with each other. A Store cannot be used simultaneously from multiple threads (almost all operations require &mut self).

  • wasmtime_runtime::InstanceHandle - this is the low-level representation of a WebAssembly instance. At the same time this is also used as the representation for all host-defined object. For example if you call wasmtime::Memory::new it'll create an InstanceHandle under the hood. This is a very unsafe type that should probably have all of its functions marked unsafe or otherwise have more strict guarantees documented about it, but it's an internal type that we don't put much thought into for public consumption at this time. An InstanceHandle doesn't know how to deallocate itself and relies on the caller to manage its memory. Currently this is either allocated on-demand (with malloc) or in a pooling fashion (using the pooling allocator). The deallocate method is different in these two paths (as well as the allocate method).

    An InstanceHandle is laid out in memory with some Rust-owned values first capturing the dynamic state of memories/tables/etc. Most of these fields are unused for host-defined objects that serve one purpose (e.g. a wasmtime::Table::new), but for an instantiated WebAssembly module these fields will have more information. After an InstanceHandle in memory is a VMContext, which will be discussed next. InstanceHandle values are the main internal runtime representation and what the wasmtime_runtime crate works with. The wasmtime::Store holds onto all these InstanceHandle values and deallocates them at the appropriate time. From the runtime perspective it simplifies things so the graph of wasm modules communicating to each other is reduced to simply InstanceHandle values all talking to themselves.

  • wasmtime_runtime::VMContext - this is a raw pointer, within an allocation of an InstanceHandle, that is passed around in JIT code. A VMContext does not have a structure defined in Rust (it's a 0-sized structure) because its contents are dynamically determined based on the VMOffsets, or the source wasm module it came from. Each InstanceHandle has a "shape" of a VMContext corresponding with it. For example a VMContext stores all values of WebAssembly globals, but if a wasm module has no globals then the size of this array will be 0 and it won't be allocated. The intention of a VMContext is to be an efficient in-memory representation of all wasm module state that JIT code may access. The layout of VMContext is dynamically determined by a module and JIT code is specialized for this one structure. This means that the structure is efficiently accessed by JIT code, but less efficiently accessed by native host code. A non-exhaustive list of purposes of the VMContext is to:

    • Store WebAssembly instance state such as global values, pointers to tables, pointers to memory, and pointers to other JIT functions.
    • Separate wasm imports and local state. Imported values have pointers stored to their actual values, and local state has the state defined inline.
    • Hold a pointer to the stack limit at which point JIT code will trigger a stack overflow.
    • Hold a pointer to a VMExternRefActivationsTable for fast-path insertion of externref values into the table.
    • Hold a pointer to a *mut dyn wasmtime_runtime::Store so store-level operations can be performed in libcalls.

    A comment about the layout of a VMContext can be found in the vmoffsets.rs file.

  • wasmtime::Module - this is the representation of a compiled WebAssembly module. At this time Wasmtime always assumes that a wasm module is always compiled to native JIT code. Module holds the results of said compilation, and currently Cranelift can be used for compiling. It is a goal of Wasmtime to support other modes of representing modules but those are not implemented today just yet, only Cranelift is implemented and supported.

  • wasmtime_environ::Module - this is a descriptor of a wasm module's type and structure without holding any actual JIT code. An instance of this type is created very early on in the compilation process, and it is not modified when functions themselves are actually compiled. This holds the internal type representation and state about functions, globals, etc. In a sense this can be thought of as the result of validation or typechecking a wasm module, although it doesn't have information such as the types of each opcode or minute function-level details like that.

Compiling a module

With a high-level overview and some background information of types, this will next walk through the steps taken to compile a WebAssembly module. The main entry point for this is the wasmtime::Module::from_binary API. There are a number of other entry points that deal with surface-level details like translation from text-to-binary, loading from the filesystem, etc.

Compilation is roughly broken down into a few phases:

  1. First compilation walks over the WebAssembly module validating everything except function bodies. This synchronous pass over a wasm module creates a wasmtime_environ::Module instance and additionally prepares for function compilation. Note that with the module linking proposal one input module may end up creating a number of output modules to process. Each module is processed independently and all further steps are parallelized on a per-module basis. Note that parsing and validation of the WebAssembly module happens with the wasmparser crate. Validation is interleaved with parsing, validating parsed values before using them.

  2. Next all functions within a module are validated and compiled in parallel. No inter-procedural analysis is done and each function is compiled as its own little island of code at this time. This is the point where the meat of Cranelift is invoked on a per-function basis.

  3. The compilation results at this point are all woven into a wasmtime_jit::CompilationArtifacts structure. This holds module information (wasmtime_environ::Module), compiled JIT code (stored as an ELF image), and miscellaneous other information about functions such as platform-agnostic unwinding information, per-function trap tables (indicating which JIT instructions can trap and what the trap means), per-function address maps (mapping from JIT addresses back to wasm offsets), and debug information (parsed from DWARF information in the wasm module). These results are inert and can't actually be executed, but they're appropriate at this point to serialize to disk or begin the next phase...

  4. The final step is to actually place all code into a form that's ready to get executed. This starts from the CompilationArtifacts of the previous step. Here a new memory mapping is allocated and the JIT code is copied into this memory mapping. This memory mapping is then switched from read/write to read/execute so it's actually executable JIT code at this point. This is where various hooks like loading debuginfo, informing JIT profilers of new code, etc, all happens. At this point a wasmtime_jit::CompiledModule is produced and this is itself wrapped up in a wasmtime::Module. At this point the module is ready to be instantiated.

A wasmtime::Module is an atomically-reference-counted object where upon instantiation into a Store the Store will hold a strong reference to the internals of the module. This means that all instances of a wasmtime::Module share the same compiled code. Additionally a wasmtime::Module is one of the few objects that lives outside of a wasmtime::Store. This means that wasmtime::Module's reference counting is its own form of memory management.

Note that the property of sharing a module's compiled code across all instantiations has interesting implications on what the compiled code can assume. For example Wasmtime implements a form of type interning, but the interned types happen at a few different levels. Within a module we deduplicate function types, but across modules in a Store types need to be represented with the same value. This means that if the same module is instantiated into many stores its same function type may take on many values, so the compiled code can't assume a particular value for a function type. (more on type information later). The general gist though is that compiled code leans relatively heavily on the VMContext for contextual input because the JIT code is intended to be so widely reusable.

Trampolines

An important aspect to also cover for compilation is the creation of trampolines. Trampolines in this case refer to code executed by Wasmtime to enter WebAssembly code. The host may not always have prior knowledge about the signature of the WebAssembly function that it wants to call. Wasmtime JIT code is compiled with native ABIs (e.g. params/results in registers according to System V on Unix), which means that a Wasmtime embedding doesn't have an easy way to enter JIT code.

This problem is what the trampolines compiled into a module solve, which is to provide a function with a known ABI that will call into a function with a specific other type signature/ABI. Wasmtime collects all the exported functions of a module and creates a set of their type signatures. Note that exported in this context actually means "possibly exported" which includes things like insertion into a global/function table, conversion to a funcref, etc. A trampoline is generated for each of these type signatures and stored along with the JIT code for the rest of the module.

These trampolines are then used with the wasmtime::Func::call API where in that specific case because we don't know the ABI of the target function the trampoline (with a known ABI) is used and has all the parameters/results passed through the stack.

Another point of note is that trampolines are not deduplicated at this time. Each compiled module contains its own set of trampolines, and if two compiled modules have the same types then they'll have different copies of the same trampoline.

Type Interning and VMSharedSignatureIndex

One important point to talk about with compilation is the VMSharedSignatureIndex type and how it's used. The call_indirect opcode in wasm compares an actual function's signature against the function signature of the instruction, trapping if the signatures mismatch. This is implemented in Wasmtime as an integer comparison, and the comparison happens on a VMSharedSignatureIndex value. This index is an intern'd representation of a function type.

The scope of interning for VMSharedSignatureIndex happens at the wasmtime::Engine level. Modules are compiled into an Engine. Insertion of a Module into an Engine will assign a VMSharedSignatureIndex to all of the types found within the module.

The VMSharedSignatureIndex values for a module are local to that one instantiation of a Module (and they may change on each insertion of a Module into a different Engine). These are used during the instantiation process by the runtime to assign a type ID effectively to all functions for imports and such.

Instantiating a module

Once a module has been compiled it's typically then instantiated to actually get access to the exports and call wasm code. Instantiation always happens within a wasmtime::Store and the created instance (plus all exports) are tied to the Store.

Instantiation itself (crates/wasmtime/src/instance.rs) may look complicated, but this is primarily due to the implementation of the Module Linking proposal. The rough flow of instantiation looks like:

  1. First all imports are type-checked. The provided list of imports is cross-referenced with the list of imports recorded in the wasmtime_environ::Module and all types are verified to line up and match (according to the core wasm specification's definition of type matching).

  2. Each wasmtime_environ::Module has a list of initializers that need to be completed before instantiation is finished. For MVP wasm this only involves loading the import into the correct index array, but for module linking this could involve instantiating other modules, handling alias fields, etc. In any case the result of this step is a wasmtime_runtime::Imports array which has the values for all imported items into the wasm module. Note that in this case an import is typically some sort of raw pointer to the actual state plus the VMContext of the instance that was imported from. The final result of this step is an InstanceAllocationRequest, which is then submitted to the configured instance allocator, either on-demand or pooling.

  3. The InstanceHandle corresponding to this instance is allocated. How this is allocated depends on the strategy (malloc for on-demand, slab allocation for pooling). In addition to initialization of the fields of InstanceHandle this also initializes all the fields of the VMContext for this handle (which as mentioned above is adjacent to the InstanceHandle allocation after it in memory). This does not process any data segments, element segments, or the start function at this time.

  4. At this point the InstanceHandle is stored within the Store. This is the "point of no return" where the handle must be kept alive for the same lifetime as the Store itself. If an initialization step fails then the instance may still have had its functions, for example, inserted into an imported table via an element segment. This means that even if we fail to initialize this instance its state could still be visible to other instances/objects so we need to keep it alive regardless.

  5. The final step is performing wasm-defined instantiation. This involves processing element segments, data segments, the start function, etc. Most of this is just translating from Wasmtime's internal representation to the specification's required behavior.

Another part worth pointing out for instantiating a module is that a ModuleRegistry is maintained within a Store of all instantiated modules into the store. The purpose of this registry is to retain a strong reference to items in the module needed to run instances. This includes the JIT code primarily but also has information such as the VMSharedSignatureIndex registration, metadata about function addresses and such, etc. Much of this data is stored into a GLOBAL_MODULES map for later access during traps.

Traps

Once instances have been created and wasm starts running most things are fairly standard. Trampolines are used to enter wasm (or we can enter with a known ABI if using wasmtime::TypedFunc) and JIT code generally does what it does to execute wasm. An important aspect of the implementation to cover, however, is traps.

Wasmtime today implements traps with longjmp and setjmp. The setjmp function cannot be defined in Rust (even unsafely -- (https://github.com/rust-lang/rfcs/issues/2625) so the crates/runtime/src/helpers.c file actually calls setjmp/longjmp. Note that in general the operation of longjmp is not safe to execute in Rust because it skips stack-based destructors, so after setjmp when we call back into Rust to execute wasm we need to be careful in Wasmtime to not have any significant destructors on the stack once wasm is called.

Traps can happen from a few different sources:

  • Explicit traps - these can happen when a host call returns a trap, for example. These bottom out in raise_user_trap or raise_lib_trap, both of which immediately call longjmp to go back to the wasm starting point. Note that these, like when calling wasm, have to have callers be very careful to not have any destructors on the stack.

  • Signals - this is the main vector for trap. Basically we use segfault and illegal instructions to implement traps in wasm code itself. Segfaults arise when linear memory accesses go out of bounds and illegal instructions are how the wasm unreachable instruction is implemented. In both of these cases Wasmtime installs a platform-specific signal handler to catch the signal, inspect the state of the signal, and then handle it. Note that Wasmtime tries to only catch signals that happen from JIT code itself as to not accidentally cover up other bugs. Exiting a signal handler happens via longjmp to get back to the original wasm call-site.

The general idea is that Wasmtime has very tight control over the stack frames of wasm (naturally via Cranelift) and also very tight control over the code that executes just before we enter wasm (aka before the setjmp) and just after we reenter back into wasm (aka frames before a possible longjmp).

The signal handler for Wasmtime uses the GLOBAL_MODULES map populated during instantiation to determine whether a program counter that triggered a signal is indeed a valid wasm trap. This should be true except for cases where the host program has another bug that triggered the signal.

A final note worth mentioning is that Wasmtime uses the Rust backtrace crate to capture a stack trace when a wasm exception occurs. This forces Wasmtime to generate native platform-specific unwinding information to correctly unwind the stack and generate a stack trace for wasm code. This does have other benefits as well such as improving generic sampling profilers when used with Wasmtime.

Linear Memory

Linear memory in Wasmtime is implemented effectively with mmap (or the platform equivalent thereof), but there are some subtle nuances that are worth pointing out here too. The implementation of linear memory is relatively configurable which gives rise to a number of situations that both the runtime and generated code need to handle.

First there are a number of properties about linear memory which can be configured:

  • wasmtime::Config::static_memory_maximum_size
  • wasmtime::Config::static_memory_guard_size
  • wasmtime::Config::dynamic_memory_guard_size
  • wasmtime::Config::guard_before_linear_memory

The methods on Config have a good bit of documentation to go over some nitty-gritty, but the general gist is that Wasmtime has two modes of memory: static and dynamic. Static memories represent an address space reservation that never moves and pages are committed to represent memory growth. Dynamic memories represent allocations where the committed portion exactly matches the wasm memory's size and growth happens by allocating a bigger chunk of memory.

The guard size configuration indicates the size of the guard region that happens after linear memory. This guard size affects whether generated JIT code emits bounds checks or not. Bounds checks are elided if out-of-bounds addresses provably encounter the guard pages.

The guard_before_linear_memory configuration additionally places guard pages in front of linear memory as well as after linear memory (the same size on both ends). This is only used to protect against possible Cranelift bugs and otherwise serves no purpose.

At the time of this writing Wasmtime only supports WebAssembly with 32-bit memories, so a maximum of 4GB in size. Wasmtime has not implemented the memory64 proposal from upstream WebAssembly yet.

The defaults for Wasmtime on 64-bit platforms are:

  • 4GB static maximum size (meaning all memories are static)
  • 2GB static guard size (meaning all loads/stores with less than 2GB offset don't need bounds checks)
  • Guard pages before linear memory are enabled.

Altogether this means that linear memories result in an 8GB virtual address space reservation by default in Wasmtime. With the pooling allocator where we know that linear memories are contiguous this results in a 6GB reservation per memory because the guard region after one memory is the guard region before the next.

Tables and externref

WebAssembly tables contain reference types, currently either funcref or externref. A funcref in Wasmtime is represented as *mut VMCallerCheckedAnyfunc and an externref is represented as VMExternRef (which is internally *mut VMExternData). Tables are consequently represented as vectors of pointers. Table storage memory management by default goes through Rust's Vec which uses malloc and friends for memory. With the pooling allocator this uses preallocated memory for storage.

As mentioned previously Store has no form of internal garbage collection for wasm objects themselves so a funcref table in wasm is pretty simple in that there's no lifetime management of any of the pointers stored within, they're simply assumed to be valid for as long as the table is in use.

For tables of externref the story is more complicated. The VMExternRef is a version of Arc<dyn Any> but specialized in Wasmtime so JIT code knows where the offset of the reference count field to directly manipulate it is. Furthermore tables of externref values need to manage the reference count field themselves, since the pointer stored in the table is required to have a strong reference count allocated to it.

GC and externref

Wasmtime implements the externref type of WebAssembly with an atomically-reference-counted pointer. Note that the atomic part is not needed by wasm itself but rather from the Rust embedding environment where it must be safe to send ExternRef values to other threads. Wasmtime also does not come with a cycle collector so cycles of host-allocated ExternRef objects will leak.

Despite reference counting, though, a Store::gc method exists. This is an implementation detail of how reference counts are managed while wasm code is executing. Instead of managing the reference count of an externref value individually as it moves around on the stack Wasmtime implements "deferred reference counting" where there's an overly conservative list of ExternRef values that may be in use, and periodically a GC is performed to make this overly conservative list a precise one. This leverages the stack map support of Cranelift plus the backtracing support of backtrace to determine live roots on the stack. The Store::gc method forces the possibly-overly-conservative list to become a precise list of externref values that are actively in use on the stack.

Index of crates

The main Wasmtime internal crates are:

  • wasmtime - the safe public API of wasmtime.
  • wasmtime-jit - JIT-specific support for Wasmtime. This is the concrete implementation that manages executable memory generated at Runtime. Currently all modules are compiled this way, but one day Wasmtime may be able to be compiled without JIT support where only precompiled modules can be loaded.
  • wasmtime-runtime - low-level runtime implementation of Wasmtime. This is where VMContext and InstanceHandle live. This crate is theoretically agnostic to how JIT code was compiled and the runtime that it's running within.
  • wasmtime-environ - low-level compilation support. This is where translation of the Module and its environment happens, although no compilation actually happens in this crate (although it defines an interface for compilers). The results of this crate are handed off to other crates for actual compilation.
  • wasmtime-cranelift - implementation of function-level compilation using Cranelift.

Note that at this time Cranelift is a required dependency of wasmtime. Most of the types exported from wasmtime-environ use cranelift types in their API. One day it's a goal, though, to remove the required cranelift dependency and have wasmtime-environ be a relatively standalone crate.

In addition to the above crates there are some other miscellaneous crates that wasmtime depends on:

  • wasmtime-cache - optional dependency to manage default caches on the filesystem. This is enabled in the CLI by default but not enabled in the wasmtime crate by default.
  • wasmtime-fiber - implementation of stack-switching used by async support in Wasmtime
  • wasmtime-debug - implementation of mapping wasm dwarf debug information to native dwarf debug information.
  • wasmtime-profiling - implementation of hooking up generated JIT code to standard profiling runtimes.
  • wasmtime-obj - implementation of creating an ELF image from compiled functions.

Building

This section describes everything required to build and run Wasmtime.

Prerequisites

Before we can actually build Wasmtime, we'll need to make sure these things are installed first.

Git Submodules

The Wasmtime repository contains a number of git submodules. To build Wasmtime and most other crates in the repository, you have to ensure that those are initialized with this command:

git submodule update --init

The Rust Toolchain

Install the Rust toolchain here. This includes rustup, cargo, rustc, etc...

libclang (optional)

The wasmtime-fuzzing crate transitively depends on bindgen, which requires that your system has a libclang installed. Therefore, if you want to hack on Wasmtime's fuzzing infrastructure, you'll need libclang. Details on how to get libclang and make it available for bindgen are here.

Building the wasmtime CLI

To make an unoptimized, debug build of the wasmtime CLI tool, go to the root of the repository and run this command:

cargo build

The built executable will be located at target/debug/wasmtime.

To make an optimized build, run this command in the root of the repository:

cargo build --release

The built executable will be located at target/release/wasmtime.

You can also build and run a local wasmtime CLI by replacing cargo build with cargo run.

Building the Wasmtime C API

To build the C API of Wasmtime you can run:

cargo build --release --manifest-path crates/c-api/Cargo.toml

This will place the shared library inside of target/release. On Linux it will be called libwasmtime.{a,so}, on macOS it will be called libwasmtime.{a,dylib}, and on Windows it will be called wasmtime.{lib,dll,dll.lib}.

Building Other Wasmtime Crates

You can build any of the Wasmtime crates by appending -p wasmtime-whatever to the cargo build invocation. For example, to build the wasmtime-jit crate, execute this command:

cargo build -p wasmtime-jit

Alternatively, you can cd into the crate's directory, and run cargo build there, without needing to supply the -p flag:

cd crates/jit/
cargo build

Testing

This section describes how to run Wasmtime's tests and add new tests.

Before continuing, make sure you can build Wasmtime successfully. Can't run the tests if you can't build it!

Running All Tests

To run all of Wasmtime's tests (excluding WASI integration tests), execute this command:

cargo test --all

To include WASI integration tests, you'll need wasm32-wasi target installed, which, assuming you're using rustup.rs to manage your Rust versions, can be done as follows:

rustup target add wasm32-wasi

Next, to run all tests including the WASI integration tests, execute this command:

cargo test --features test-programs/test_programs --all

You can also exclude a particular crate from testing with --exclude. For example, if you want to avoid testing the wastime-fuzzing crate — which requires that libclang is installed on your system, and for some reason maybe you don't have it — you can run:

cargo test --all --exclude wasmtime-fuzzing

Testing a Specific Crate

You can test a particular Wasmtime crate with cargo test -p wasmtime-whatever. For example, to test the wasmtime-environ crate, execute this command:

cargo test -p wasmtime-environ

Alternatively, you can cd into the crate's directory, and run cargo test there, without needing to supply the -p flag:

cd crates/environ/
cargo test

Running the Wasm Spec Tests

The spec testsuite itself is in a git submodule, so make sure you've checked it out and initialized its submodule:

git submodule update --init

When the submodule is checked out, Wasmtime runs the Wasm spec testsuite as part of testing the wasmtime-cli crate:

cargo test -p wasmtime-cli

Running WASI Integration Tests Only

WASI integration tests can be run separately from all other tests which can be useful when working on the wasi-common crate. This can be done by executing this command:

cargo test --features test-programs/test_programs -p test-programs

Adding New Tests

Adding Rust's #[test]-Style Tests

For very "unit-y" tests, we add test modules in the same .rs file as the code that is being tested. These test modules are configured to only get compiled during testing with #[cfg(test)].

#![allow(unused)]
fn main() {
// some code...

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn some_test_for_that_code() {
        // ...
    }
}
}

If you're writing a unit test and a test module doesn't already exist, you can create one.

For more "integration-y" tests, we create a tests directory within the crate, and put the tests inside there. For example, there are various code cache-related tests at crates/environ/tests/cache_*.rs. Always feel free to add a tests directory to a crate, if you want to add a new test and there aren't any existing tests.

Adding Specification-Style Wast Tests

We use the spec testsuite as-is and without custom patches or a forked version. This probably isn't what you want to modify when adding a new Wasmtime test!

When you have a Wasmtime-specific test that you'd like to write in Wast and use the Wast-style assertions, you can add it to our "misc testsuite". The misc testsuite uses the same syntax and assertions as the spec testsuite, but lives in tests/misc_testsuite. Feel free to add new tests to existing tests/misc_testsuite/*.wast files or create new ones as needed. These tests are run as part of the wasmtime-cli crate's tests.

If you have a new test that you think really belongs in the spec testsuite, make sure it makes sense for every Wasm implementation to run your test (i.e. it isn't Wasmtime-specific) and send a pull request upstream. Once it is accepted in the upstream repo, we can update our git submodule and we'll start running the new tests.

Adding WASI Integration Tests

When you have a WASI-specific test program that you'd like to include as a test case to run against our WASI implementation, you can add it to our test-programs crate. In particular, you should drop a main-style Rust source file into crates/test-programs/wasi-tests/src/bin/some_new_test.rs with a name of your choice. And that's it! The build script included in the test-programs crate will automatically generate the necessary boilerplate code for your test program so that it's run on all supported hosts.

If you would like to tweak which host to run the test program against however (for instance, only on Unix but on Windows), you can tweak that in the build script located under crates/test-programs/build.rs.

Fuzzing

Test Case Generators and Oracles

Test case generators and oracles live in the wasmtime-fuzzing crate, located in the crates/fuzzing directory.

A test case generator takes raw, unstructured input from a fuzzer and translates that into a test case. This might involve interpreting the raw input as "DNA" or pre-determined choices through a decision tree and using it to generate an in-memory data structure, or it might be a no-op where we interpret the raw bytes as if they were Wasm.

An oracle takes a test case and determines whether we have a bug. For example, one of the simplest oracles is to take a Wasm binary as an input test case, validate and instantiate it, and (implicitly) check that no assertions failed or segfaults happened. A more complicated oracle might compare the result of executing a Wasm file with and without optimizations enabled, and make sure that the two executions are observably identical.

Our test case generators and oracles strive to be fuzzer-agnostic: they can be reused with libFuzzer or AFL or any other fuzzing engine or driver.

libFuzzer and cargo fuzz Fuzz Targets

We combine a test case generator and one more more oracles into a fuzz target. Because the target needs to pipe the raw input from a fuzzer into the test case generator, it is specific to a particular fuzzer. This is generally fine, since they're only a couple of lines of glue code.

Currently, all of our fuzz targets are written for libFuzzer and cargo fuzz. They are defined in the fuzz subdirectory.

See fuzz/README.md for details on how to run these fuzz targets and set up a corpus of seed inputs.

Continuous Integration (CI)

The Wasmtime and Cranelift projects heavily rely on Continuous Integration (CI) to ensure everything keeps working and keep the final end state of the code at consistently high quality. The CI setup for this repository is relatively involved and extensive, and so it's worth covering here how it's organized and what's expected of contributors.

All CI currently happens on GitHub Actions and is configured in the .github directory of the repository.

PRs and CI

Currently the full CI test suite runs on every Pull Request. All PRs need to have that lovely green checkmark before being candidates for being merged. If a test is failing you'll want to check out the logs on CI and fix it before the PR can be merged.

PR authors are expected to fix CI failures in their PR, unless the CI failure is systemic and unrelated to the PR. In that case other maintainers should be alerted to ensure that the problem can be addressed.

Tests run on CI

While this may not be fully exhaustive, the general idea of all the checks we run on CI looks like this:

  • Code formatting - we run cargo fmt -- --check on CI to ensure that all code in the repository is formatted with rustfmt. All PRs are expected to be formatted with the latest stable version of rustfmt.

  • Book documentation tests - code snippets (Rust ones at least) in the book documentation (the docs folder) are tested on CI to ensure they are working.

  • Crate tests - the moral equivalent of cargo test --all and cargo test --all --release is executed on CI. This means that all workspace crates have their entire test suite run, documentation tests and all, in both debug and release mode. Additionally we execute all crate tests on macOS, Windows, and Linux, to ensure that everything works on all the platforms.

  • Fuzz regression tests - we take a random sampling of the fuzz corpus and run it through the fuzzers. This is mostly intended to be a pretty quick regression test and testing the fuzzers still build, most of our fuzzing happens on oss-fuzz. Found issues are recorded in the oss-fuzz bug tracker

While we do run more tests here and there, this is the general shape of what you can be expected to get tested on CI for all commits and all PRs. You can of course always feel free to expand our CI coverage by editing the CI files themselves, we always like to run more tests!

Artifacts produced on CI

Our CI system is also responsible for producing all binary releases and documentation of Wasmtime and Cranelift. Currently this consists of:

  • Tarballs of the wasmtime CLI - produced for macOS, Windows, and Linux we try to make these "binary compatible" wherever possible, for example producing the Linux build in a really old CentOS container to have a very low glibc requirement.

  • Tarballs of the Python extension - also produced on the main three platforms these wheels are compiled on each commit.

  • Book and API documentation - the book is rendered with mdbook and we also build all documentation with cargo doc.

Artifacts are produced for every single commit and every single PR. You should be able to find a downloadable version of all artifacts produced on the "runs" page in GitHub Actions. For example here's an example job, and if you're looking at a specific builder you can see the artifacts link in the top right. Note that artifacts don't become available until the whole run finishes.

Commits merged into the main branch will rerun CI and will also produce artifacts as usual. On the main branch, however, documentation is pushed to the gh-pages branch as well, and binaries are pushed to the dev release on GitHub. Finally, tagged commits get a whole dedicated release to them too.

Cross Compiling

When contributing to Wasmtime and Cranelift you may run into issues that only reproduce on a different architecture from your development machine. Luckily, cargo makes cross compilation and running tests under QEMU pretty easy.

This guide will assume you are on an x86-64 with Ubuntu/Debian as your OS. The basic approach (with commands, paths, and package names appropriately tweaked) applies to other Linux distributions as well.

On Windows you can install build tools for AArch64 Windows, but targeting platforms like Linux or macOS is not easy. While toolchains exist for targeting non-Windows platforms you'll have to hunt yourself to find the right one.

On macOS you can install, through Xcode, toolchains for iOS but the main x86_64-apple-darwin is really the only easy target to install. You'll need to hunt for toolchains if you want to compile for Linux or Windows.

Install Rust Targets

First, use rustup to install Rust targets for the other architectures that Wasmtime and Cranelift support:

$ rustup target add \
    s390x-unknown-linux-gnu \
    riscv64gc-unknown-linux-gnu \
    aarch64-unknown-linux-gnu

Install GCC Cross-Compilation Toolchains

Next, you'll need to install a gcc for each cross-compilation target to serve as a linker for rustc.

$ sudo apt install \
    gcc-s390x-linux-gnu \
    gcc-riscv64-linux-gnu \
    gcc-aarch64-linux-gnu

Install qemu

You will also need to install qemu to emulate the cross-compilation targets.

$ sudo apt install qemu-user

Configure Cargo

The final bit to get out of the way is to configure cargo to use the appropriate gcc and qemu when cross-compiling and running tests for other architectures.

Add this to .cargo/config.toml in the Wasmtime repository (or create that file if none already exists).

[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
runner = "qemu-aarch64 -L /usr/aarch64-linux-gnu -E LD_LIBRARY_PATH=/usr/aarch64-linux-gnu/lib -E WASMTIME_TEST_NO_HOG_MEMORY=1"

[target.riscv64gc-unknown-linux-gnu]
linker = "riscv64-linux-gnu-gcc"
runner = "qemu-riscv64 -L /usr/riscv64-linux-gnu -E LD_LIBRARY_PATH=/usr/riscv64-linux-gnu/lib -E WASMTIME_TEST_NO_HOG_MEMORY=1"

[target.s390x-unknown-linux-gnu]
linker = "s390x-linux-gnu-gcc"
runner = "qemu-s390x -L /usr/s390x-linux-gnu -E LD_LIBRARY_PATH=/usr/s390x-linux-gnu/lib -E WASMTIME_TEST_NO_HOG_MEMORY=1"

Cross-Compile Tests and Run Them!

Now you can use cargo build, cargo run, and cargo test as you normally would for any crate inside the Wasmtime repository, just add the appropriate --target flag!

A few examples:

  • Build the wasmtime binary for aarch64:

    $ cargo build --target aarch64-unknown-linux-gnu
    
  • Run the tests under riscv emulation:

    $ cargo test --target riscv64gc-unknown-linux-gnu
    
  • Run the wasmtime binary under s390x emulation:

    $ cargo run --target s390x-unknown-linux-gnu -- compile example.wasm
    

Coding guidelines

For the most part, Wasmtime and Cranelift follow common Rust conventions and pull request (PR) workflows, though we do have a few additional things to be aware of.

rustfmt

All PRs must be formatted according to rustfmt, and this is checked in the continuous integration tests. You can format code locally with:

$ cargo fmt

at the root of the repository. You can find more information about rustfmt online too, such as how to configure your editor.

Minimum Supported rustc Version

Wasmtime supports the current stable version of Rust.

Cranelift supports stable Rust, and follows the Rust Update Policy for Firefox.

Some of the developer scripts depend on nightly Rust, for example to run clippy and other tools, however we avoid depending on these for the main build.

Development Process

We use issues for asking questions (open one here!) and tracking bugs and unimplemented features, and pull requests (PRs) for tracking and reviewing code submissions.

Before submitting a PR

Consider opening an issue to talk about it. PRs without corresponding issues are appropriate for fairly narrow technical matters, not for fixes to user-facing bugs or for feature implementations, especially when those features might have multiple implementation strategies that usefully could be discussed.

Our issue templates might help you through the process.

When submitting PRs

  • Please fill in the pull request template as appropriate. It is usually helpful, it speeds up the review process and helps understanding the changes brought by the PR.

  • Write clear commit messages that start with a one-line summary of the change (and if it's difficult to summarize in one line, consider splitting the change into multiple PRs), optionally followed by additional context. Good things to mention include which areas of the code are affected, which features are affected, and anything that reviewers might want to pay special attention to.

  • If there is code which needs explanation, prefer to put the explanation in a comment in the code, or in documentation, rather than in the commit message.

  • For pull requests that fix existing issues, use issue keywords. Note that not all pull requests need to have accompanying issues.

  • Assign the review to somebody from the Core Team, either using suggestions in the list proposed by Github, or somebody else if you have a specific person in mind.

  • When updating your pull request, please make sure to re-request review if the request has been cancelled.

Focused commits or squashing

We generally squash sequences of incremental-development commits together into logical commits (though keeping logical commits focused). Developers may do this themselves before submitting a PR or during the PR process, or Core Team members may do it when merging a PR. Ideally, the continuous-integration tests should pass at each logical commit.

Review and merge

Anyone may submit a pull request, and anyone may comment on or review others' pull requests. However, one review from somebody in the Core Team is required before the Core Team merges it.

Even Core Team members should create PRs for every change, including minor work items (version bump, removing warnings, etc.): this is helpful to keep track of what has happened on the repository. Very minor changes may be merged without a review, although it is always preferred to have one.

Release Process

This is intended to serve as documentation for Wasmtime's release process. It's largely an internal checklist for those of us performing a Wasmtime release, but others might be curious in this as well!

Releasing a major version

Major versions of Wasmtime are relased once-a-month. Most of this is automatic and all that needs to be done is to merge GitHub PRs that CI will generate. At a high-level the structure of Wasmtime's release process is:

  • On the 5th of every month a new release-X.Y.Z branch is created with the current contents of main.
  • On the 20th of every month this release branch is published to crates.io and release artifacts are built.

This means that Wasmtime releases are always at least two weeks behind development on main and additionally happen once a month. The lag time behind main is intended to give time to fuzz changes on main as well as allow testing for any users using main. It's expected, though, that most consumers will likely use the release branches of wasmtime.

A detailed list of all the steps in the release automation process are below. The steps requiring interactions are bolded, otherwise everything else is automatic and this is documenting what automation does.

  1. On the 5th of every month, (configured via .github/workflows/release-process.yml) a CI job will run and do these steps:
    • Download the current main branch
    • Push the main branch to release-X.Y.Z
    • Run ./scripts/publish.rs with the bump argument
    • Commit the changes
    • Push these changes to a temporary ci/* branch
    • Open a PR with this branch against main
    • This step can also be triggered manually with the main branch and the cut argument.
  2. A maintainer of Wasmtime merges this PR
    • It's intended that this PR can be immediately merged as the release branch has been created and all it's doing is bumping the version.
  3. Time passes and the release-X.Y.Z branch is maintained
    • All changes land on main first, then are backported to release-X.Y.Z as necessary.
    • Even changes to RELEASES.md are pushed to main first.
  4. On the 20th of every month (same CI job as before) another CI job will run performing:
    • Download the current main branch.
    • Update the release date of X.Y.Z to today in RELEASES.md
    • Open a PR against main for this change
    • Reset to release-X.Y.Z
    • Update the release date of X.Y.Z to today in RELEASES.md
    • Add a special marker to the commit message to indicate a tag should be made.
    • Open a PR against release-X.Y.Z for this change
    • This step can also be triggered manually with the main branch and the release-latest argument.
  5. A maintainer of Wasmtime merges these two PRs
    • The PR against main is a small update to the release notes and should be mergeable immediately.
    • The PR against release-X.Y.Z, when merged, will trigger the next steps due to the marker in the commit message. A maintainer should double-check there are no open security issues, but otherwise it's expected that all other release issues are resolved by this point.
  6. The .github/workflow/push-tag.yml workflow is triggered on all commits including the one just created with a PR merge. This workflow will:
    • Scan the git logs of pushed changes for the special marker added by release-process.yml.
    • If found, tags the current main commit and pushes that to the main repository.
  7. Once a tag is created CI runs in full on the tag itself. CI for tags will create a GitHub release with release artifacts and it will also publish crates to crates.io. This is orchestrated by .github/workflows/main.yml.

If all goes well you won't have to read up much on this and after hitting the Big Green Button for the automatically created PRs everything will merrily carry on its way.

Releasing a patch version

Making a patch release is somewhat more manual than a major version, but like before there's automation to help guide the process as well and take care of more mundane bits.

This is a list of steps taken to perform a patch release for 2.0.1 for example. Like above human interaction is indicated with bold text in these steps.

  1. Necessary changes are backported to the release-2.0.0 branch from main
    • All changes must land on main first (if applicable) and then get backported to an older branch. Release branches should already exist from the above major release steps.
    • CI may not have been run in some time for release branches so it may be necessary to backport CI fixes and updates from main as well.
    • When merging backports maintainers need to double-check that the PUBLIC_CRATES listed in scripts/publish.rs do not have semver-API-breaking changes (in the strictest sense). All security fixes must be done in such a way that the API doesn't break between the patch version and the original version.
    • Don't forget to write patch notes in RELEASES.md for backported changes.
  2. The patch release process is triggered manually with the release-2.0.0 branch and the release-patch argument
    • This will run the release-process.yml workflow. The scripts/publish.rs script will be run with the bump-patch argument.
    • The changes will be committed with a special marker indicating a release needs to be made.
    • A PR will be created from a temporary ci/* branch to the release-2.0.0 branch which, when merged, will trigger the release process.
  3. Review the generated PR and merge it
    • This will resume from step 6 above in the major release process where the special marker in the commit message generated by CI will trigger a tag to get pushed which will further trigger the rest of the release process.

After a patch release has been made you'll also want to double-check that the release notes on the patch branch are in sync with the main branch.

Releasing a security patch

When making a patch release that has a security-related fix the contents of the patch are often kept private until the day of the patch release which means that the process here is slightly different from the patch release process above. In addition the precise runbook is currently under discussion in an RFC for security patches, so this intends to document what we've been doing so far and it'll get updated when the runbook is merged.

  1. The fix for the security issue is developed in a GitHub Security Advisory
    • This will not have any CI run, it's recommended to run ./ci/run-tests.sh locally at least.
    • This will also only be the patch for the main branch. You'll need to locally maintain and develop patches for any older releases being backported to. Note that from the major release process there should already be a branch for all older releases.
  2. Send a PR for the version bump when an email goes out announcing there will be a security release
    • An email is sent to the bytecodealliance security mailing list ahead of a patch release to announce that a patch release will happen. At this time you should trigger the version bump against the appropriate release-x.y.z branch with the release-patch argument.
    • This will send a PR, but you should not merge it. Instead use this PR and the time ahead of the security release to fix any issues with CI. Older release-x.y.z branches haven't run CI in awhile so they may need to backport fixes of one variety or another. DO NOT include the actual fix for the security issue into the PR, that comes in the next step.
  3. Make the patches public
    • For the main branch this will involve simply publishing the GitHub Security Advisory. Note that CI will run after the advisory's changes are merged in on main.
    • For the backported release branches you should either create a PR targeting these branches or push the changes to the previous version-bump PRs.
  4. Merge the version-bump PR
    • Like the patch release process this will kick everything else into motion. Note that the actual security fixes should be merged either before or as part of this PR.

After a security release has been made you'll also want to double-check that the release notes on the branch are in sync with the main branch.

Implementing WebAssembly Proposals

Adding New Support for a Wasm Proposal

The following checkboxes enumerate the steps required to add support for a new WebAssembly proposal to Wasmtime. They can be completed over the course of multiple pull requests.

  • Add support to the wasmparser crate.

  • Add support to the wat and wast crates.

  • Add support to the wasmprinter crate.

  • Add support to the wasm-encoder crate.

  • Add support to the wasm-smith crate.

  • Add a wasmtime::Config::enable_foo_bar method to the wasmtime crate.

  • Add a --enable-foo-bar command line flag to the wasmtime binary.

  • Enable the spec tests in build.rs but mark them as ignored for now.

  • Stop ignoring individual spec tests and get them passing one by one.

  • Enable the proposal in the fuzz targets.

    • Add examples from the spec tests to the relevant corpora.

      The wast2json tool from WABT is useful for this.

    • Write a custom fuzz target, oracle, and/or test case generator for fuzzing this proposal in particular.

      For example, we wrote a custom generator, oracle, and fuzz target for exercising table.{get,set} instructions and their interaction with GC while implementing the reference types proposal.

  • Expose the proposal's new functionality in the wasmtime crate's API.

    For example, the bulk memory operations proposal introduced a table.copy instruction, and we exposed its functionality as the wasmtime::Table::copy method.

  • Expose the proposal's new functionality in the C API.

    This may require extensions to the standard C API, and if so, should be defined in wasmtime.h and prefixed with wasmtime_.

  • Use the C API to expose the proposal's new functionality in the other language embedding APIs:

  • Document support for the proposal in wasmtime/docs/stability-wasm-proposals-support.md.

Enabling Support for a Proposal by Default

These are the standards that must be met to enable support for a proposal by default in Wasmtime, and can be used as a review checklist.

  • The proposal must be in phase 4, or greater, of the WebAssembly standardization process.

  • All spec tests must be passing in Wasmtime.

  • No open questions, design concerns, or serious known bugs.

  • Has been fuzzed for at least a week minimum.

  • We are confident that the fuzzers are fully exercising the proposal's functionality.

    For example, it would not have been enough to simply enable reference types in the compile fuzz target to enable that proposal by default. Compiling a module that uses reference types but not instantiating it nor running any of its functions doesn't exercise any of the GC implementation and does not run the inline fast paths for table operations emitted by the JIT. Exercising these things was the motivation for writing the custom fuzz target for table.{get,set} instructions.

  • The proposal's functionality is exposed in the wasmtime crate's API.

  • The proposal's functionality is exposed in the C API.

  • The proposal's functionality is exposed in at least one of the other languages' APIs.

Governance

... more coming soon

Contributor Covenant Code of Conduct

Note: this Code of Conduct pertains to individuals' behavior. Please also see the Organizational Code of Conduct.

Our Pledge

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.

Our Standards

Examples of behavior that contributes to creating a positive environment include:

  • Using welcoming and inclusive language
  • Being respectful of differing viewpoints and experiences
  • Gracefully accepting constructive criticism
  • Focusing on what is best for the community
  • Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

  • The use of sexualized language or imagery and unwelcome sexual attention or advances
  • Trolling, insulting/derogatory comments, and personal or political attacks
  • Public or private harassment
  • Publishing others' private information, such as a physical or electronic address, without explicit permission
  • Other conduct which could reasonably be considered inappropriate in a professional setting

Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the Bytecode Alliance CoC team at report@bytecodealliance.org. The CoC team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The CoC team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the Bytecode Alliance's leadership.

Attribution

This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at http://contributor-covenant.org/version/1/4