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:
- How to create simple wasm modules
- How to use Wasmtime from a number of languages
- How to install and use the
wasmtime
CLI - Information about stability and security in Wasmtime.
... 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
This is an explanation of all examples to come
... more coming soon
Markdown Parser
... more coming soon
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 withProfilingStrategy::JitDump
to enable profiling of your wasm modules. -
C API - you'll want to call the
wasmtime_config_profiler_set
API with aWASMTIME_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
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
Using VTune
on Linux
VTune Profiler
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 command line or gui, provides a variety of options for viewing and doing anaysis on that data. VTune Profiler is available in both commerical and free options. The free download version backed by a community forum for support, is available here
. This version is appropriate for detailed analysis of your WASM program. Note for jit support, Wasmtime only supports VTune profiling on linux platforms but other platforms are expected to be enabled in the future.
VTune support in wasmtime is provided through the jit profiling APIs at https://github.com/intel/ittapi
. These APIs are provided for code generators (or the runtimes that use them) to report jit activities. These APIs are implemented in a shared library (built from the same ittapi
project) which wasmtime pulls in and 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, it links to this same shared library to handle profiling request related to the reported jit activities. Specifically, Wasmtime pulls in the ittapi-rs system crate which provides the shared library and Rust interface to the jit profiling APIs.
For jit profiling with VTune Profiler, first you want to make sure the vtune
feature is enabled. After that, enabling runtime support is based on how you are using Wasmtime:
-
Rust API - you'll want to call the [
Config::profiler
] method withProfilingStrategy::VTune
to enable profiling of your wasm modules. -
C API - you'll want to call the
wasmtime_config_profiler_set
API with aWASMTIME_PROFILING_STRATEGY_VTUNE
value. -
Command Line - you'll want to pass the
--vtune
flag on the command line.
After profiling is complete, a results folder will hold profiling data that can then be read and analyzed with VTune.
Also note, VTune is capable of profiling a single process or system wide. As such, and like perf, VTune is plenty capable of profiling the wasmtime runtime itself without any added support. However, APIs here
also support an interface for marking the start and stop of code regions for easy isolatation in the VTune Profiler. Support for these APIs are expected to be added in the future.
Take the following example: with VTune properly installed, if you're using the CLI you'll execute with:
$ cargo build --features=vtune
$ amplxe-cl -run-pass-thru=--no-altstack -collect hotspots target/debug/wasmtime --vtune foo.wasm
This command tells the VTune collector (amplxe-cl) to collect hotspot profiling data on wasmtime that 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 hotspot 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.
VTune
example
Running through a familiar algorithm, first we'll start with the following wasm:
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) } }
Profiling data using vtune can be collected a number of ways and profiling data can be collected to focus on certain types of analysis. Below we show a command line executable option using amplxe-cl, which is installed and in our path, to help find hotspots in our wasm module. To collect profiling information then, we'll simply execute:
$ rustc --target wasm32-wasi fib.rs -C opt-level=z -C lto=yes
$ amplxe-cl -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
Note again, wasmtime must be built with the vtune
feature flag enabled. From here you there are several options for further analysis. Below is an example view of the collected as seen in VTune's gui with it's many options.
For more information on VTune and the analysis tools it provides see the docs here
.
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 specific
section on Rust embedding or 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.
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::*;
fn main() -> Result<()> {
// Configure the initial compilation environment, creating the global
// `Store` structure. Note that you can also tweak configuration settings
// with a `Config` and an `Engine` if desired.
println!("Initializing...");
let store = Store::default();
// Compile the wasm binary into an in-memory instance of a `Module`.
println!("Compiling module...");
let module = Module::from_file(store.engine(), "examples/hello.wat")?;
// Here we handle the imports of the module, which in this case is our
// `HelloCallback` type and its associated implementation of `Callback.
println!("Creating callback...");
let hello_func = Func::wrap(&store, || {
println!("Calling back...");
println!("> Hello World!");
});
// 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(&store, &module, &imports)?;
// Next we poke around a bit to extract the `run` function from the module.
println!("Extracting export...");
let run = instance
.get_func("run")
.ok_or(anyhow::format_err!("failed to find `run` function export"))?
.get0::<()>()?;
// And last but not least we can call it!
println!("Calling export...");
run()?;
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 store = Store::default();
let module = Module::from_file(store.engine(), "examples/gcd.wat")?;
let instance = Instance::new(&store, &module, &[])?;
// Invoke `gcd` export
let gcd = instance
.get_func("gcd")
.ok_or(anyhow::format_err!("failed to find `gcd` function export"))?
.get2::<i32, i32, i32>()?;
println!("gcd(6, 27) = {}", gcd(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` context and then compile a module and create an
// instance from the compiled module all in one go.
let wasmtime_store = Store::default();
let module = Module::from_file(wasmtime_store.engine(), "examples/memory.wat")?;
let instance = Instance::new(&wasmtime_store, &module, &[])?;
// Load up our exports from the instance
let memory = instance
.get_memory("memory")
.ok_or(anyhow::format_err!("failed to find `memory` export"))?;
let size = instance
.get_func("size")
.ok_or(anyhow::format_err!("failed to find `size` export"))?
.get0::<i32>()?;
let load = instance
.get_func("load")
.ok_or(anyhow::format_err!("failed to find `load` export"))?
.get1::<i32, i32>()?;
let store = instance
.get_func("store")
.ok_or(anyhow::format_err!("failed to find `store` export"))?
.get2::<i32, i32, ()>()?;
// Note that these memory reads are *unsafe* due to unknown knowledge about
// aliasing with wasm memory. For more information about the safety
// guarantees here and how to use `Memory` safely, see the API
// documentation.
println!("Checking memory...");
assert_eq!(memory.size(), 2);
assert_eq!(memory.data_size(), 0x20000);
unsafe {
assert_eq!(memory.data_unchecked_mut()[0], 0);
assert_eq!(memory.data_unchecked_mut()[0x1000], 1);
assert_eq!(memory.data_unchecked_mut()[0x1003], 4);
}
assert_eq!(size()?, 2);
assert_eq!(load(0)?, 0);
assert_eq!(load(0x1000)?, 1);
assert_eq!(load(0x1003)?, 4);
assert_eq!(load(0x1ffff)?, 0);
assert!(load(0x20000).is_err()); // out of bounds trap
println!("Mutating memory...");
unsafe {
memory.data_unchecked_mut()[0x1003] = 5;
}
store(0x1002, 6)?;
assert!(store(0x20000, 0).is_err()); // out of bounds trap
unsafe {
assert_eq!(memory.data_unchecked_mut()[0x1002], 6);
assert_eq!(memory.data_unchecked_mut()[0x1003], 5);
}
assert_eq!(load(0x1002)?, 6);
assert_eq!(load(0x1003)?, 5);
// Grow memory.
println!("Growing memory...");
memory.grow(1)?;
assert_eq!(memory.size(), 3);
assert_eq!(memory.data_size(), 0x30000);
assert_eq!(load(0x20000)?, 0);
store(0x20000, 0)?;
assert!(load(0x30000).is_err());
assert!(store(0x30000, 0).is_err());
assert!(memory.grow(1).is_err());
assert!(memory.grow(0).is_ok());
println!("Creating stand-alone memory...");
let memorytype = MemoryType::new(Limits::new(5, Some(5)));
let memory2 = Memory::new(&wasmtime_store, memorytype);
assert_eq!(memory2.size(), 5);
assert!(memory2.grow(1).is_err());
assert!(memory2.grow(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 off how to instantiate a wasm module using WASI imports.
Wasm Source code
fn main() { println!("Hello, world!"); }
wasi.rs
//! Example of instantiating of instantiating a wasm module which uses WASI
//! imports.
// You can execute this example with `cargo run --example wasi`
use anyhow::Result;
use wasi_cap_std_sync::WasiCtxBuilder;
use wasmtime::*;
use wasmtime_wasi::Wasi;
fn main() -> Result<()> {
tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.with_ansi(true)
.init();
let store = Store::default();
let mut linker = Linker::new(&store);
// Create an instance of `Wasi` which contains a `WasiCtx`. Note that
// `WasiCtx` provides a number of ways to configure what the target program
// will have access to.
let wasi = Wasi::new(
&store,
WasiCtxBuilder::new()
.inherit_stdio()
.inherit_args()?
.build()?,
);
wasi.add_to_linker(&mut linker)?;
// Instantiate our module with the imports we've created, and run it.
let module = Module::from_file(store.engine(), "target/wasm32-wasi/debug/wasi.wasm")?;
linker.module("", &module)?;
linker.get_default("")?.get0::<()>()?()?;
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.
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 wasi_cap_std_sync::WasiCtxBuilder;
use wasmtime::*;
use wasmtime_wasi::Wasi;
fn main() -> Result<()> {
let engine = Engine::default();
let store = Store::new(&engine);
// 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(&store);
let wasi = Wasi::new(
&store,
WasiCtxBuilder::new()
.inherit_stdio()
.inherit_args()?
.build()?,
);
wasi.add_to_linker(&mut linker)?;
// Load and compile our two modules
let linking1 = Module::from_file(&engine, "examples/linking1.wat")?;
let linking2 = Module::from_file(&engine, "examples/linking2.wat")?;
// 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(&linking2)?;
linker.instance("linking2", &linking2)?;
// And with that we can perform the final link and the execute the module.
let linking1 = linker.instantiate(&linking1)?;
let run = linking1.get_func("run").unwrap();
let run = run.get0::<()>()?;
run()?;
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 store = Store::new(&engine);
let module = Module::from_file(&engine, "target/wasm32-unknown-unknown/debug/fib.wasm")?;
let instance = Instance::new(&store, &module, &[])?;
// Invoke `fib` export
let fib = instance
.get_func("fib")
.ok_or(anyhow::format_err!("failed to find `fib` function export"))?
.get1::<i32, i32>()?;
println!("fib(6) = {}", fib(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::{format_err, Result};
use wasmtime::*;
fn main() -> Result<()> {
println!("Initializing...");
let engine = Engine::default();
let store = Store::new(&engine);
// Compile.
println!("Compiling module...");
let module = Module::from_file(&engine, "examples/multi.wat")?;
// Create external print functions.
println!("Creating callback...");
let callback_type = FuncType::new(
[ValType::I32, ValType::I64].iter().cloned(),
[ValType::I64, ValType::I32].iter().cloned(),
);
let callback_func = Func::new(&store, callback_type, |_, args, results| {
println!("Calling back...");
println!("> {} {}", args[0].unwrap_i32(), args[1].unwrap_i64());
results[0] = Val::I64(args[1].unwrap_i64() + 1);
results[1] = Val::I32(args[0].unwrap_i32() + 1);
Ok(())
});
// Instantiate.
println!("Instantiating module...");
let instance = Instance::new(&store, &module, &[callback_func.into()])?;
// Extract exports.
println!("Extracting export...");
let g = instance
.get_func("g")
.ok_or(format_err!("failed to find export `g`"))?;
// Call `$g`.
println!("Calling export \"g\"...");
let results = g.call(&[Val::I32(1), Val::I64(3)])?;
println!("Printing result...");
println!("> {} {}", results[0].unwrap_i64(), results[1].unwrap_i32());
assert_eq!(results[0].unwrap_i64(), 4);
assert_eq!(results[1].unwrap_i32(), 2);
// Call `$round_trip_many`.
println!("Calling export \"round_trip_many\"...");
let round_trip_many = instance
.get_func("round_trip_many")
.ok_or(format_err!("failed to find export `round_trip_many`"))?;
let args = vec![
Val::I64(0),
Val::I64(1),
Val::I64(2),
Val::I64(3),
Val::I64(4),
Val::I64(5),
Val::I64(6),
Val::I64(7),
Val::I64(8),
Val::I64(9),
];
let results = round_trip_many.call(&args)?;
println!("Printing result...");
print!(">");
for r in results.iter() {
print!(" {}", r.unwrap_i64());
}
println!();
assert_eq!(results.len(), 10);
assert!(args
.iter()
.zip(results.iter())
.all(|(a, r)| a.i64() == r.i64()));
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.
*/
#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(const wasm_val_vec_t* args, wasm_val_vec_t* results) {
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.
wasm_store_t *store = wasm_store_new(engine);
assert(store != NULL);
// 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, &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");
wasm_module_t *module = NULL;
error = wasmtime_module_new(engine, &wasm, &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.
printf("Creating callback...\n");
wasm_functype_t *hello_ty = wasm_functype_new_0_0();
wasm_func_t *hello = wasm_func_new(store, hello_ty, hello_callback);
// 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;
wasm_instance_t *instance = NULL;
wasm_extern_t* imports[] = { wasm_func_as_extern(hello) };
wasm_extern_vec_t imports_vec = WASM_ARRAY_VEC(imports);
error = wasmtime_instance_new(store, module, &imports_vec, &instance, &trap);
if (instance == NULL)
exit_with_error("failed to instantiate", error, trap);
// Lookup our `run` export function
printf("Extracting export...\n");
wasm_extern_vec_t externs;
wasm_instance_exports(instance, &externs);
assert(externs.size == 1);
wasm_func_t *run = wasm_extern_as_func(externs.data[0]);
assert(run != NULL);
// And call it!
printf("Calling export...\n");
wasm_val_vec_t args_vec = WASM_EMPTY_VEC;
wasm_val_vec_t results_vec = WASM_EMPTY_VEC;
error = wasmtime_func_call(run, &args_vec, &results_vec, &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;
wasm_extern_vec_delete(&externs);
wasm_instance_delete(instance);
wasm_module_delete(module);
wasm_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.
*/
#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);
wasm_store_t *store = wasm_store_new(engine);
assert(store != NULL);
// 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, &wasm);
if (error != NULL)
exit_with_error("failed to parse wat", error, NULL);
wasm_byte_vec_delete(&wat);
// Compile and instantiate our module
wasm_module_t *module = NULL;
error = wasmtime_module_new(engine, &wasm, &module);
if (module == NULL)
exit_with_error("failed to compile module", error, NULL);
wasm_byte_vec_delete(&wasm);
wasm_trap_t *trap = NULL;
wasm_instance_t *instance = NULL;
wasm_extern_vec_t imports = WASM_EMPTY_VEC;
error = wasmtime_instance_new(store, module, &imports, &instance, &trap);
if (instance == NULL)
exit_with_error("failed to instantiate", error, trap);
// Lookup our `gcd` export function
wasm_extern_vec_t externs;
wasm_instance_exports(instance, &externs);
assert(externs.size == 1);
wasm_func_t *gcd = wasm_extern_as_func(externs.data[0]);
assert(gcd != NULL);
// And call it!
int a = 6;
int b = 27;
wasm_val_t params[2] = { WASM_I32_VAL(a), WASM_I32_VAL(b) };
wasm_val_t results[1];
wasm_val_vec_t params_vec = WASM_ARRAY_VEC(params);
wasm_val_vec_t results_vec = WASM_ARRAY_VEC(results);
error = wasmtime_func_call(gcd, ¶ms_vec, &results_vec, &trap);
if (error != NULL || trap != NULL)
exit_with_error("failed to call gcd", error, trap);
assert(results[0].kind == WASM_I32);
printf("gcd(%d, %d) = %d\n", a, b, results[0].of.i32);
// Clean up after ourselves at this point
ret = 0;
wasm_extern_vec_delete(&externs);
wasm_instance_delete(instance);
wasm_module_delete(module);
wasm_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.
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);
wasm_memory_t* get_export_memory(const wasm_extern_vec_t* exports, size_t i) {
if (exports->size <= i || !wasm_extern_as_memory(exports->data[i])) {
printf("> Error accessing memory export %zu!\n", i);
exit(1);
}
return wasm_extern_as_memory(exports->data[i]);
}
wasm_func_t* get_export_func(const wasm_extern_vec_t* exports, size_t i) {
if (exports->size <= i || !wasm_extern_as_func(exports->data[i])) {
printf("> Error accessing function export %zu!\n", i);
exit(1);
}
return wasm_extern_as_func(exports->data[i]);
}
void check(bool success) {
if (!success) {
printf("> Error, expected success\n");
exit(1);
}
}
void check_call(wasm_func_t* func, const wasm_val_vec_t* args_vec, int32_t expected) {
wasm_val_t results[1];
wasm_val_vec_t results_vec = WASM_ARRAY_VEC(results);
wasm_trap_t *trap = NULL;
wasmtime_error_t *error = wasmtime_func_call(func, args_vec, &results_vec, &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(wasm_func_t* func, int32_t expected) {
wasm_val_vec_t args_vec = WASM_EMPTY_VEC;
check_call(func, &args_vec, expected);
}
void check_call1(wasm_func_t* func, int32_t arg, int32_t expected) {
wasm_val_t args[] = { WASM_I32_VAL(arg) };
wasm_val_vec_t args_vec = WASM_ARRAY_VEC(args);
check_call(func, &args_vec, expected);
}
void check_call2(wasm_func_t* func, int32_t arg1, int32_t arg2, int32_t expected) {
wasm_val_t args[] = { WASM_I32_VAL(arg1), WASM_I32_VAL(arg2) };
wasm_val_vec_t args_vec = WASM_ARRAY_VEC(args);
check_call(func, &args_vec, expected);
}
void check_ok(wasm_func_t* func, const wasm_val_vec_t* args_vec) {
wasm_trap_t *trap = NULL;
wasm_val_vec_t results_vec = WASM_EMPTY_VEC;
wasmtime_error_t *error = wasmtime_func_call(func, args_vec, &results_vec, &trap);
if (error != NULL || trap != NULL)
exit_with_error("failed to call function", error, trap);
}
void check_ok2(wasm_func_t* func, int32_t arg1, int32_t arg2) {
wasm_val_t args[] = { WASM_I32_VAL(arg1), WASM_I32_VAL(arg2) };
wasm_val_vec_t args_vec = WASM_ARRAY_VEC(args);
check_ok(func, &args_vec);
}
void check_trap(wasm_func_t* func, const wasm_val_vec_t* args_vec, size_t num_results) {
assert(num_results <= 1);
wasm_val_t results[1];
wasm_val_vec_t results_vec;
results_vec.data = results;
results_vec.size = num_results;
wasm_trap_t *trap = NULL;
wasmtime_error_t *error = wasmtime_func_call(func, args_vec, &results_vec, &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(wasm_func_t* func, int32_t arg) {
wasm_val_t args[] = { WASM_I32_VAL(arg) };
wasm_val_vec_t args_vec = WASM_ARRAY_VEC(args);
check_trap(func, &args_vec, 1);
}
void check_trap2(wasm_func_t* func, int32_t arg1, int32_t arg2) {
wasm_val_t args[] = { WASM_I32_VAL(arg1), WASM_I32_VAL(arg2) };
wasm_val_vec_t args_vec = WASM_ARRAY_VEC(args);
check_trap(func, &args_vec, 0);
}
int main(int argc, const char* argv[]) {
// Initialize.
printf("Initializing...\n");
wasm_engine_t* engine = wasm_engine_new();
wasm_store_t* store = wasm_store_new(engine);
// 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, &binary);
if (error != NULL)
exit_with_error("failed to parse wat", error, NULL);
wasm_byte_vec_delete(&wat);
// Compile.
printf("Compiling module...\n");
wasm_module_t* module = NULL;
error = wasmtime_module_new(engine, &binary, &module);
if (error)
exit_with_error("failed to compile module", error, NULL);
wasm_byte_vec_delete(&binary);
// Instantiate.
printf("Instantiating module...\n");
wasm_instance_t* instance = NULL;
wasm_trap_t *trap = NULL;
wasm_extern_vec_t imports = WASM_EMPTY_VEC;
error = wasmtime_instance_new(store, module, &imports, &instance, &trap);
if (!instance)
exit_with_error("failed to instantiate", error, trap);
// Extract export.
printf("Extracting exports...\n");
wasm_extern_vec_t exports;
wasm_instance_exports(instance, &exports);
size_t i = 0;
wasm_memory_t* memory = get_export_memory(&exports, i++);
wasm_func_t* size_func = get_export_func(&exports, i++);
wasm_func_t* load_func = get_export_func(&exports, i++);
wasm_func_t* store_func = get_export_func(&exports, i++);
wasm_module_delete(module);
// Try cloning.
wasm_memory_t* copy = wasm_memory_copy(memory);
wasm_memory_delete(copy);
// Check initial memory.
printf("Checking memory...\n");
check(wasm_memory_size(memory) == 2);
check(wasm_memory_data_size(memory) == 0x20000);
check(wasm_memory_data(memory)[0] == 0);
check(wasm_memory_data(memory)[0x1000] == 1);
check(wasm_memory_data(memory)[0x1003] == 4);
check_call0(size_func, 2);
check_call1(load_func, 0, 0);
check_call1(load_func, 0x1000, 1);
check_call1(load_func, 0x1003, 4);
check_call1(load_func, 0x1ffff, 0);
check_trap1(load_func, 0x20000);
// Mutate memory.
printf("Mutating memory...\n");
wasm_memory_data(memory)[0x1003] = 5;
check_ok2(store_func, 0x1002, 6);
check_trap2(store_func, 0x20000, 0);
check(wasm_memory_data(memory)[0x1002] == 6);
check(wasm_memory_data(memory)[0x1003] == 5);
check_call1(load_func, 0x1002, 6);
check_call1(load_func, 0x1003, 5);
// Grow memory.
printf("Growing memory...\n");
check(wasm_memory_grow(memory, 1));
check(wasm_memory_size(memory) == 3);
check(wasm_memory_data_size(memory) == 0x30000);
check_call1(load_func, 0x20000, 0);
check_ok2(store_func, 0x20000, 0);
check_trap1(load_func, 0x30000);
check_trap2(store_func, 0x30000, 0);
check(! wasm_memory_grow(memory, 1));
check(wasm_memory_grow(memory, 0));
wasm_extern_vec_delete(&exports);
wasm_instance_delete(instance);
// Create stand-alone memory.
printf("Creating stand-alone memory...\n");
wasm_limits_t limits = {5, 5};
wasm_memorytype_t* memorytype = wasm_memorytype_new(&limits);
wasm_memory_t* memory2 = wasm_memory_new(store, memorytype);
check(wasm_memory_size(memory2) == 5);
check(! wasm_memory_grow(memory2, 1));
check(wasm_memory_grow(memory2, 0));
wasm_memorytype_delete(memorytype);
wasm_memory_delete(memory2);
// Shut down.
printf("Shutting down...\n");
wasm_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:
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.
*/
#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() {
int ret = 0;
// Set up our context
wasm_engine_t *engine = wasm_engine_new();
assert(engine != NULL);
wasm_store_t *store = wasm_store_new(engine);
assert(store != 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
wasm_module_t *module = NULL;
wasmtime_error_t *error = wasmtime_module_new(engine, &wasm, &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;
wasi_instance_t *wasi = wasi_instance_new(store, "wasi_snapshot_preview1", wasi_config, &trap);
if (wasi == NULL)
exit_with_error("failed to instantiate WASI", NULL, trap);
wasmtime_linker_t *linker = wasmtime_linker_new(store);
error = wasmtime_linker_define_wasi(linker, wasi);
if (error != NULL)
exit_with_error("failed to link wasi", error, NULL);
// Instantiate the module
wasm_name_t empty;
wasm_name_new_from_string(&empty, "");
wasm_instance_t *instance = NULL;
error = wasmtime_linker_module(linker, &empty, module);
if (error != NULL)
exit_with_error("failed to instantiate module", error, NULL);
// Run it.
wasm_func_t* func;
wasmtime_linker_get_default(linker, &empty, &func);
if (error != NULL)
exit_with_error("failed to locate default export for module", error, NULL);
wasm_val_vec_t args_vec = WASM_EMPTY_VEC;
wasm_val_vec_t results_vec = WASM_EMPTY_VEC;
error = wasmtime_func_call(func, &args_vec, &results_vec, &trap);
if (error != NULL)
exit_with_error("error calling default export", error, trap);
// Clean up after ourselves at this point
wasm_name_delete(&empty);
wasm_module_delete(module);
wasm_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.
*/
#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() {
int ret = 0;
// Set up our context
wasm_engine_t *engine = wasm_engine_new();
assert(engine != NULL);
wasm_store_t *store = wasm_store_new(engine);
assert(store != NULL);
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;
wasm_module_t *linking1_module = NULL;
wasm_module_t *linking2_module = NULL;
error = wasmtime_module_new(engine, &linking1_wasm, &linking1_module);
if (error != NULL)
exit_with_error("failed to compile linking1", error, NULL);
error = wasmtime_module_new(engine, &linking2_wasm, &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);
// 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;
wasi_instance_t *wasi = wasi_instance_new(store, "wasi_snapshot_preview1", wasi_config, &trap);
if (wasi == 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(store);
error = wasmtime_linker_define_wasi(linker, wasi);
if (error != NULL)
exit_with_error("failed to link wasi", error, NULL);
// Instantiate `linking2` with our linker.
wasm_instance_t *linking2;
error = wasmtime_linker_instantiate(linker, 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
wasm_name_t linking2_name;
linking2_name.data = "linking2";
linking2_name.size = strlen(linking2_name.data);
error = wasmtime_linker_define_instance(linker, &linking2_name, linking2);
if (error != NULL)
exit_with_error("failed to link linking2", error, NULL);
// Instantiate `linking1` with the linker now that `linking2` is defined
wasm_instance_t *linking1;
error = wasmtime_linker_instantiate(linker, linking1_module, &linking1, &trap);
if (error != NULL || trap != NULL)
exit_with_error("failed to instantiate linking1", error, trap);
// Lookup our `run` export function
wasm_extern_vec_t linking1_externs;
wasm_instance_exports(linking1, &linking1_externs);
assert(linking1_externs.size == 1);
wasm_func_t *run = wasm_extern_as_func(linking1_externs.data[0]);
assert(run != NULL);
wasm_val_vec_t args_vec = WASM_EMPTY_VEC;
wasm_val_vec_t results_vec = WASM_EMPTY_VEC;
error = wasmtime_func_call(run, &args_vec, &results_vec, &trap);
if (error != NULL || trap != NULL)
exit_with_error("failed to call run", error, trap);
// Clean up after ourselves at this point
wasm_extern_vec_delete(&linking1_externs);
wasm_instance_delete(linking1);
wasm_instance_delete(linking2);
wasmtime_linker_delete(linker);
wasm_module_delete(linking1_module);
wasm_module_delete(linking2_module);
wasm_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, 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);
wasm_store_t* store = wasm_store_new(engine);
// 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");
wasm_module_t *module = NULL;
wasmtime_error_t* error = wasmtime_module_new(engine, &binary, &module);
if (!module)
exit_with_error("failed to compile module", error, NULL);
wasm_byte_vec_delete(&binary);
// Figure out which export is the `fib` export
wasm_exporttype_vec_t module_exports;
wasm_module_exports(module, &module_exports);
int fib_idx = -1;
for (int i = 0; i < module_exports.size; i++) {
const wasm_name_t *name = wasm_exporttype_name(module_exports.data[i]);
if (name->size != 3)
continue;
if (strncmp("fib", name->data, 3) != 0)
continue;
fib_idx = i;
break;
}
wasm_exporttype_vec_delete(&module_exports);
if (fib_idx == -1) {
printf("> Error finding `fib` export!\n");
return 1;
}
// Instantiate.
printf("Instantiating module...\n");
wasm_instance_t* instance = NULL;
wasm_trap_t *trap = NULL;
wasm_extern_vec_t imports = WASM_EMPTY_VEC;
error = wasmtime_instance_new(store, module, &imports, &instance, &trap);
if (error != NULL || trap != NULL)
exit_with_error("failed to instantiate", error, trap);
wasm_module_delete(module);
// Extract export.
printf("Extracting export...\n");
own wasm_extern_vec_t exports;
wasm_instance_exports(instance, &exports);
if (exports.size == 0) {
printf("> Error accessing exports!\n");
return 1;
}
// Getting second export (first is memory).
wasm_func_t* run_func = wasm_extern_as_func(exports.data[fib_idx]);
if (run_func == NULL) {
printf("> Error accessing export!\n");
return 1;
}
wasm_instance_delete(instance);
// Call.
printf("Calling fib...\n");
wasm_val_t params[1] = { WASM_I32_VAL(6) };
wasm_val_t results[1];
wasm_val_vec_t params_vec = WASM_ARRAY_VEC(params);
wasm_val_vec_t results_vec = WASM_ARRAY_VEC(results);
error = wasmtime_func_call(run_func, ¶ms_vec, &results_vec, &trap);
if (error != NULL || trap != NULL)
exit_with_error("failed to call function", error, trap);
wasm_extern_vec_delete(&exports);
printf("> fib(6) = %d\n", results[0].of.i32);
// Shut down.
printf("Shutting down...\n");
wasm_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.
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(
const wasm_val_vec_t* args, wasm_val_vec_t* results
) {
printf("Calling back...\n");
printf("> %"PRIu32" %"PRIu64"\n", args->data[0].of.i32, args->data[1].of.i64);
printf("\n");
wasm_val_copy(&results->data[0], &args->data[1]);
wasm_val_copy(&results->data[1], &args->data[0]);
return NULL;
}
// A function closure.
wasm_trap_t* closure_callback(
void* env, const wasm_val_t args[], wasm_val_t results[]
) {
int i = *(int*)env;
printf("Calling back closure...\n");
printf("> %d\n", i);
results[0].kind = WASM_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();
wasm_store_t* store = wasm_store_new(engine);
// 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, &binary);
if (error != NULL)
exit_with_error("failed to parse wat", error, NULL);
wasm_byte_vec_delete(&wat);
// Compile.
printf("Compiling module...\n");
wasm_module_t* module = NULL;
error = wasmtime_module_new(engine, &binary, &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()
);
wasm_func_t* callback_func =
wasm_func_new(store, callback_type, callback);
wasm_functype_delete(callback_type);
// Instantiate.
printf("Instantiating module...\n");
wasm_extern_t* imports[] = {wasm_func_as_extern(callback_func)};
wasm_extern_vec_t imports_vec = WASM_ARRAY_VEC(imports);
wasm_instance_t* instance = NULL;
wasm_trap_t* trap = NULL;
error = wasmtime_instance_new(store, module, &imports_vec, &instance, &trap);
if (!instance)
exit_with_error("failed to instantiate", error, trap);
wasm_func_delete(callback_func);
// Extract export.
printf("Extracting export...\n");
wasm_extern_vec_t exports;
wasm_instance_exports(instance, &exports);
if (exports.size == 0) {
printf("> Error accessing exports!\n");
return 1;
}
wasm_func_t* run_func = wasm_extern_as_func(exports.data[0]);
if (run_func == NULL) {
printf("> Error accessing export!\n");
return 1;
}
wasm_module_delete(module);
wasm_instance_delete(instance);
// Call.
printf("Calling export...\n");
wasm_val_t args[2] = { WASM_I32_VAL(1), WASM_I64_VAL(2) };
wasm_val_t results[2];
wasm_val_vec_t args_vec = WASM_ARRAY_VEC(args);
wasm_val_vec_t results_vec = WASM_ARRAY_VEC(results);
error = wasmtime_func_call(run_func, &args_vec, &results_vec, &trap);
if (error != NULL || trap != NULL)
exit_with_error("failed to call run", error, trap);
wasm_extern_vec_delete(&exports);
// Print result.
printf("Printing result...\n");
printf("> %"PRIu64" %"PRIu32"\n",
results[0].of.i64, results[1].of.i32);
assert(results[0].kind == WASM_I64);
assert(results[0].of.i64 == 2);
assert(results[1].kind == WASM_I32);
assert(results[1].of.i32 == 1);
// Shut down.
printf("Shutting down...\n");
wasm_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 = "0.18.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>> { let engine = Engine::default(); // A `Store` is a sort of "global object" in a sense, but for now it suffices // to say that it's generally passed to most constructors. let store = Store::new(&engine); if false { // We start off by creating a `Module` which represents a compiled form // 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))"#)?; // After we have a compiled `Module` we can then instantiate it, creating // an `Instance` which we can actually poke at functions on. let instance = Instance::new(&store, &module, &[])?; // The `Instance` gives us access to various exported functions and items, // which we access here to pull out our `answer` exported function and // run it. let answer = instance.get_func("answer") .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 `get0` (in this case asserting // it takes no arguments and returns one i32) and then call it. let answer = answer.get0::<i32>()?; // 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()?; 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::*; fn main() -> Result<(), Box<dyn Error>> { let engine = Engine::default(); let store = Store::new(&engine); if false { let module = Module::from_file(&engine, "hello.wat")?; } let module = Module::new(&engine, r#"(module (import "" "log" (func $log (param i32))) (import "" "double" (func $double (param i32) (result i32))) (func (export "run") i32.const 0 call $log i32.const 1 call $log i32.const 2 call $double call $log))"#)?; // First we can create our `log` function, which will simply print out the // parameter it receives. let log = Func::wrap(&store, |param: i32| { println!("log: {}", param); }); // Next we can create our double function which doubles the input it receives. let double = Func::wrap(&store, |param: i32| param * 2); // When instantiating the module we now need to provide the imports to the // instantiation process. This is the second slice argument, where each // entry in the slice must line up with the imports in the module. let instance = Instance::new(&store, &module, &[log.into(), double.into()])?; let run = instance .get_func("run") .expect("`run` was not an exported function"); let run = run.get0::<()>()?; Ok(run()?) }
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, 'gcd.wat')
instance = Instance(module, [])
gcd = instance.get_export('gcd')
print("gcd(27, 6) =", gcd(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!
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
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.Extern{})
check(err)
gcd := instance.GetExport("gcd").Func()
val, err := gcd.Call(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:
- Each tagged release will have a full set of release artifacts on the GitHub releases page.
- The
dev
release is also continuously updated with the latest build of themain
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
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.
wasm2obj
This is an experimental subcommand to compile a WebAssembly module to native code. Work for this is still heavily under development, but you can execute this with:
$ wasmtime wasm2obj foo.wasm foo.o
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
-
Read the statistics file for the cache file, increase the usage counter and write it back to the disk.
-
Attempt recompressing the cache file if all of the following conditions are met:
- usage counter exceeds
optimized-compression-usage-counter-threshold
, - the file is compressed with compression level lower than
optimized-compression-level
, - no other worker has started working on this particular task within the last
optimizing-compression-task-timeout
interval.
When recompressing,
optimized-compression-level
is used as a compression level. - usage counter exceeds
On UPDATE request
- Write a fresh statistics file for the cache file.
- Clean up the cache if no worker has attempted to do this within the last
cleanup-interval
. During this task:- all unrecognized files and expired task locks in cache directory will be deleted
- if
file-count-soft-limit
orfiles-total-size-soft-limit
is exceeded, then recognized files will be deleted according tofile-count-limit-percent-if-deleting
andfiles-total-size-limit-percent-if-deleting
. Wasmtime uses Least Recently Used (LRU) cache replacement policy and requires that the filesystem maintains proper mtime (modification time) of the files. Files with future mtimes are treated specially - more details inallowed-clock-drift-for-files-from-future
.
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 usingwasmtime
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 inwasmtime
, common conveniences likeprintln!
orpanic!
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 withwasmtime
.
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.
AssemblyScript
AssemblyScript 0.10.0 includes support for targeting WASI. To use it, add
import "wasi"
at the top of your entrypoint file.
To create a program which can be run directly as a command, pass --runtime half
to the AssemblyScript linker. This selects the half runtime, which ensures that
the generated wasm module doesn't contain any extraneous exports. (This isn't
strictly required today, but the handling of extraneous exports may change in
the future, so it's encouraged. As a bonus, it also reduces code size.)
To create a program which can be loaded as a library and used from other modules, no special options are needed.
Let's walk through a simple hello world example.
wasi-hello-world.ts
import "wasi"
import {Console} from "as-wasi"
Console.log('Hello World!\n');
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 -b wasi-hello-world.wasm -t wasi-hello-world.wat --runtime half",
"wasmtime": "wasmtime wasi-hello-world.wasm"
},
"author": "Aaron Turner",
"license": "MIT",
"devDependencies": {
"assemblyscript": "^0.10.0"
},
"dependencies": {
"as-wasi": "^0.1.1"
}
}
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 engine = Engine::default(); let store = Store::new(&engine); let wat = r#" (module (func (export "add") (param i32 i32) (result i32) local.get 0 local.get 1 i32.add)) "#; let module = Module::new(&engine, wat)?; let instance = Instance::new(&store, &module, &[])?; let add = instance.get_func("add").unwrap(); let add = add.get2::<i32, i32, i32>()?; println!("1 + 2 = {}", add(1, 2)?); Ok(()) }
Example: Markdown Parser
Stability
... more coming soon
Release Process
... more coming soon
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 requiresmmap
(or an equivalent), and presence ofmmap
often implies presence of a libc which means Rust'sstd
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 getstd
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 makingwasmtime
compile withoutstd
. -
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 usingstd
instead ofcore
, it's just that often code usingstd
has more dependencies (likestd::thread
) which requires code to bind. Code size improvements can be made to code usingstd
andcore
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 otherwisestd
build, which means that to supoprt this feature it must be tested on CI with ano_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 ofstd
) and data structures have to all be manually imported fromalloc
. 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 usemmap
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 Proposal | Supported in Wasmtime? | Command Line Flag | Config Method |
---|---|---|---|
Import and Export Mutable Globals | Yes. Always enabled. | (none) | (none) |
Sign-Extension Operations | Yes. Always enabled. | (none) | (none) |
Non-Trapping Float-to-Int Conversions | Yes. Always enabled. | (none) | (none) |
Multi-Value | Yes. Enabled by default. | --enable-multi-value | wasm_multi_value |
Bulk Memory Operations | Yes. Enabled by default. | --enable-bulk-memory | wasm_bulk_memory |
Reference Types | Yes. Enabled by default. | --enable-reference-types | wasm_reference_types |
Fixed-Width SIMD | In progress. | --enable-simd | wasm_simd |
Threads and Atomics | In progress. | --enable-threads | wasm_threads |
Multi-Memory | Yes. | --enable-multi-memory | wasm_multi_memory |
Module Linking | Yes. | --enable-module-linking | wasm_module_linking |
Security
... more coming soon
Disclosure Policy
... more coming soon
Sandboxing
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.
More will be added here over time!
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.
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 does not yet implement Spectre mitigations, however this is a subject of ongoing research.
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!
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.
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
Cross Compiling Wasmtime
By default cargo build
will build Wasmtime for the platform you're running the
build on. You might, however, want to build Wasmtime for a different platform!
Let's say for example that you want to build Wasmtime for
aarch64-unknown-linux-gnu
. First you'll want to acquire the Rust standard
library for this target:
rustup target add aarch64-unknown-linux-gnu
Next you need to install a native C toolchain which has a C compiler, runtime libraries, and linker for the desired target. This is unfortunately not very easy to acquire on most platforms:
-
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. -
On Linux you can relatively easily compile for other Linux architectures most of the time. For example on Debian-based distributions you can install the
gcc-aarch64-linux-gnu
package which should come with the C compiler, runtime libraries, and linker all in one (assuming you don't explicitly request disabling recommended packages). Other Linux distributions may have differently named toolchains. Compiling for macOS from Linux will require finding your own toolchain. Compiling for Windows MSVC will require finding your own toolchain, but compiling for MinGW can work easily enough if you install the MinGW toolchain via your package manager.
For now we'll assume you're on Linux compiling for a different Linux
architecture. Once you've got the native toolchain, you'll want to find the C
compiler that came with it. On Debian, for example, this is called
aarch64-linux-gnu-gcc
. Next up you'll need to configure two environment
variables to configure the Rust build:
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc
export CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc
The first environment variable tells Cargo to tell rustc what the correct linker
for your target is. The second configures the cc
Rust
crate for C code compiled as part of the build.
Finally you can execute.
cargo build --target aarch64-unknown-linux-gnu --release
The built executable will be located at
target/aarch64-unknown-linux-gnu/release/wasmtime
. Note that you can
cross-compile the C API in the same manner as the CLI too.
Note that if you are using these invocations regularly, you can avoid the need
to set environment variables by adding some configuration to your persistent
Cargo configuration. In the file ~/.cargo/config.toml
(in your home
directory), add the section:
[target.aarch64-unknown-linux-gnu]
linker = 'aarch64-linux-gnu-gcc'
Then the above cargo build --target aarch64-unknown-linux-gnu
command should
work without setting any extra environment variables beforehand.
Running a Cross-Compiled Wasmtime in qemu (emulation)
Once you have cross-compiled a binary, it is possible to run it on an emulator
if you do not have access to (or do not wish to use) hardware with the given
architecture. This can be done using an emulator such as qemu
. The qemu
user-space emulation support allows running, for example, a Linux/aarch64
binary on a Linux/x86-64 host, as long as you have the system libraries for
aarch64 as well.
To try this out, first install qemu
, making sure that the user-space emulator
option for your target architecture is enabled. On Debian-based Linux
distributions (including Ubuntu), this is in the qemu-user
package, for
example.
Next, make sure that you have system libraries for the target. You will already have these present if you cross-compiled as described above.
Finally, you can run the wasmtime
binary under qemu
; the following example
is for an aarch64
target. Adjust the library paths as appropriate; these are
correct for Ubuntu/Debian's cross-compilation packages.
qemu-aarch64 \
-L /usr/aarch64-linux-gnu \
-E LD_LIBRARY_PATH=/usr/aarch64-linux-gnu/lib \
target/aarch64-unknown-linux-gnu/release/wasmtime [ARGS]
You can add this to your persistent Cargo configuration as well. Extending the
above example in ~/.cargo/config.toml
, you can add:
[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"
Then a simple cargo test --target aarch64-unknown-linux-gnu
should work.
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
andcargo 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.
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 withcargo 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.
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!
To kick off the release process someone decides to do a release. Currently there's not a schedule for releases or something similar. Once the decision is made (there's also not really a body governing these decisions, it's more whimsical currently, or on request from others) then the following steps need to be executed to make the release:
git pull
- make sure you've got the latest changes- Run
rustc scripts/publish.rs
- Run
./publish bump
- Review and commit the changes
- Note that this bumps all cranelift/wasmtime versions as a major version bump
at this time. See the
bump_version
function inpublish.rs
to tweak this.
- Make sure
RELEASES.md
is up-to-date, and fill it out if it doesn't have an entry yet for the current release. - Send this version update as a PR to the
wasmtime
repository, wait for a merge - After merging, tag the merge as
vA.B.C
- Push the tag to the repository
- This will trigger the release CI which will create all release artifacts and publish them to GitHub releases.
- Run
./publish publish
- This will fail on some crates, but that's expected.
- Keep running this script until all crates are published. Note that crates.io won't let you publish something twice so rerunning is only for crates which need the index to be udpated and if it hasn't yet. It's recommended to wait a bit between runs of the script.
And that's it, then you've done a Wasmtime release.
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
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 thewasmtime
crate. -
Add a
--enable-foo-bar
command line flag to thewasmtime
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 thewasmtime::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 withwasmtime_
. -
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 fortable
operations emitted by the JIT. Exercising these things was the motivation for writing the custom fuzz target fortable.{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