Introduction
The cargo-wasi
project is a subcommand for
Cargo which provides a convenient set of
defaults for building and running Rust code
on the wasm32-wasi
target. The cargo wasi
command
makes compiling Rust code to WASI buttery-smooth with built-in defaults to avoid
needing to manage a myriad of tools as part of building a wasm executable.
WASI is a developing standard and we hope to make it very easy to develop Rust code for WASI to both influence the standard as well as ensure that Rust code follows WASI best practices. Keep reading for more information about how this all works!
Installation
To install cargo-wasi
you'll first want to install Rust
itself, which you'll need anyway for
building Rust code! Once you've got Rust installed you can install cargo-wasi
with:
$ cargo install cargo-wasi
This will install a precompiled binary for most major platforms or install from source if we don't have a precompiled binary for your platform. If you would like to see a precompiled binary for your platform please file an issue!.
To verify that your installation works, you can execute:
$ cargo wasi --version
and that should print both the version number as well as git information about where the binary was built from.
Now that everything is set, let's build some code for wasi!
Building from Source
Installing from crates.io via cargo install cargo-wasi
will install
precompiled binaries. These binaries are built on the cargo-wasi
repository's
CI and are uploaded to crates.io as part of the publication process. If you'd
prefer to install from source, you can execute this command instead:
$ cargo install cargo-wasi-src
Hello, World!
Let's see an example of how to run the WASI version of "Hello, World!". This'll end up looking very familiar to the Rust version of "Hello, World!" as well. First up let's create a new project with Cargo:
$ cargo new wasi-hello-world
Created binary (application) `wasi-hello-world` package
$ cd wasi-hello-world
This creates a wasi-hello-world
folder which has a default Cargo.toml
and
src/main.rs
. The main.rs
is the entry point for our program and currently
contains println!("Hello, world!");
. Everything should be set up for us to
execute (no code needed!) so let's run the code inside of the wasi-hello-world
directory:
$ cargo wasi run
error: failed to find `wasmtime` in $PATH, you'll want to install `wasmtime` before running this command
...
Oh dear, that failed very quickly! For this command though we need to have a
way to actually execute the WebAssembly binary that Rust will produce. The
cargo wasi
subcommand by default supports wasmtime,
and the error message should have instructions of how to install wasmtime
.
You can also view installation instructions on the wasmtime
website.
Once we've got wasmtime
installed, make sure it's working via:
$ wasmtime --version
Note that you may have to open a new shell for this to ensure PATH
changes
take effect.
Ok, now that we've got a runtime installed, let's retry executing our binary:
$ cargo wasi run
info: downloading component 'rust-std' for 'wasm32-wasi'
info: installing component 'rust-std' for 'wasm32-wasi'
Compiling wasi-hello-world v0.1.0 (/code/wasi-hello-world)
Finished dev [unoptimized + debuginfo] target(s) in 0.15s
Running `/.cargo/bin/cargo-wasi target/wasm32-wasi/debug/wasi-hello-world.wasm`
Running `target/wasm32-wasi/debug/wasi-hello-world.wasm`
Hello, world!
Success! The command first used
rustup
to install the Rust
wasm32-wasi
target automatically, and then we executed cargo
to build the
WebAssembly binary. Finally wasmtime
was used and we can see that Hello, world!
was printed by our program.
After this we're off to the races in developing our crate. Be sure to check out
the rest of this book for more information about what you can do with cargo wasi
. Additionally if this is your first time using Cargo, be sure to check
out Cargo's introductory
documentation as well
Steps run by cargo wasi
The cargo wasi
subcommand is intended to be a convenience when developing
Rust code for WASI, but is not required. It is a thin wrapper around the general
"toolchain" of building WebAssembly code. Building WebAssembly code can be
relatively involved and have a nontrivial number of moving parts, so having a
convenience like cargo wasi
becomes quite nice quite quickly, but it's
important to also understand what cargo wasi
is doing under the hood!
This section will explain the various steps that cargo wasi
internally takes
care of for you. Be sure to check out the reference
documentation for an exhaustive list of ways to run and configure
cargo wasi
.
Managing the wasm32-wasi
target
The Rust installer does not install the wasm32-wasi
Rust standard library by
default, but to compile any code for wasm32-wasi
you'll need to be sure to
have this target installed for your Rust toolchain. The cargo wasi
subcommand
will automatically execute, if necessary:
rustup target add wasm32-wasi
For systems not using rustup
it will generate an error indicating whether or
not the wasm32-wasi
target is installed.
Ensuring a wasmtime
runtime is installed
As we saw previously when running "Hello, world!" a
wasmtime
executable is required to execute WASI code locally. The cargo wasi
subcommand will verify that it is installed and provide an understandable error
message if it isn't, also recommending how to install
wasmtime
.
Automatically configure Cargo for wasm32-wasi
Whenever cargo wasi
is used it will automatically pass --target wasm32-wasi
to all Cargo subcommands that are invoked. This avoids you having to type
this all out on each command.
Further optimizing WebAssembly with wasm-opt
The Rust compiler usese LLVM's WebAssembly backend to produce WebAssembly code.
LLVM itself is an extremely good optimizing compiler, but LLVM's WebAssembly
backend is unfortunately not quite as optimized as its other backends (such as
X86). Standard practice today is to execute the wasm-opt
tool (part of the
binaryen project) to further
optimize a WebAssembly binary.
For LLVM-optimized WebAssembly binaries wasm-opt
normally doesn't get much of
a runtime speed increase, but it can often reduce the size of a WebAssembly
binary by 10-20%, which can be some serious savings!
For more information about how wasm-opt
is run see the reference
documentation
Executing wasm-bindgen
for WebAssembly Interface Types
The WebAssembly Interface Types proposal is a developing standard for enhancing the set of types that a WebAssembly module can work with at its boundaries (as opposed to just integers and floats). This developing standard is targeted at use cases primarily outside of a browser (but also in one!) which is a perfect fit for WASI.
Rust's support for WebAssembly Interface Types comes through the
wasm-bindgen
project. When
using wasm-bindgen
as a crate, though, it requires also executing the
matching CLI wasm-bindgen
tool on the final WebAssembly binary. The
cargo wasi
subcommand will automatically find and install the matching binary
to run on your WASI WebAssembly file. Using cargo wasi
will also
automatically configure wasm-bindgen
to enable interface types support.
Deleting DWARF debuginfo in release mode
The standard Rust toolchain, following the convention of all platforms, ships
an optimized standard library for the wasm32-wasi
target that contains DWARF
debug information. This is typically what you want in debug builds to have
a better debugging experience for the standard library, but release builds of
WebAssembly are often focused on size and disable debug information by default.
Following standard practice for all targets the Rust toolchain will by default
include the standard library's DWARF debug information in the final *.wasm
file, but cargo wasi
will strip it out.
Note that this strip only happens if your build disables debuginfo in a release
executable. If you enable debuginfo in the release executable, then cargo wasi
will not strip out the dwarf debug information.
Demangling Rust symbols in the name
section
WebAssembly's name
custom
section
is present in debug and release builds of WebAssembly binaries, but Rust
symbols, like all other platforms, are mangled! This means that instead of
main
you'll see _ZN4main20h...
, very long symbol names.
The cargo wasi
toolchain will ensure that all Rust symbol names in the name
section are demangled into a more human-readable form, improving the debugging
experience when using native tooling.
Configuration for the name
and producers
Custom Sections
WebAssembly has a name
custom
section
for providing debug names to functions/locals/etc which assist in debugging
WebAssembly modules. Additionally a producers
custom
section
is typically used to collect metadata about tools used to produce a WebAssembly
binary.
These two sections are emitted by default into all *.wasm
binaries (including
release builds). Using cargo wasi
, though, you can ensure they're
deleted from release builds in your Cargo.toml
:
[package.metadata]
wasm-name-section = false
wasm-producers-section = false
More information about configuration can be found in the reference
Reference
This section of documentation is a reference-style documentation about cargo wasi
. It's intended to be an exhaustive document of features provided by
cargo wasi
, so if you don't see something documented here please file an
issue!
CLI Usage
In general cargo wasi
takes no CLI flags specifically, since it will forward
everything to cargo
under the hood. The subcommand, however, will attempt
to infer flags such as -v
from the Cargo arguments pass, switching itself to
a verbose output if it looks like Cargo is using a verbose output.
The supported subcommands for cargo wasi
are:
cargo wasi build
This is the primary subcommand used to build WebAssembly code. This will build
your crate for the wasm32-wasi
target and run any postprocessing (like
wasm-bindgen
or wasm-opt
) over any produced binary.
$ cargo wasi build
$ cargo wasi build --release
$ cargo wasi build --lib
$ cargo wasi build --test foo
Output *.wasm
files will be located in target/wasm32-wasi/debug
for debug
builds or target/wasm32-wasi/release
for release builds.
cargo wasi check
This subcommands forwards everything to cargo check
, allowing to perform
quick compile-time checks over your code without actually producing any
*.wasm
binaries or running any wasm code.
$ cargo wasi check
$ cargo wasi check --lib
$ cargo wasi check --tests
cargo wasi run
Forwards everything to cargo run
, and runs all binaries in wasmtime
.
Arguments passed will be forwarded to wasmtime
. Note that it's not
necessary to run cargo wasi build
before this subcommand. Example usage looks
like:
$ cargo wasi run
$ cargo wasi run --release
$ cargo wasi run arg1 arg2
$ cargo wasi run -- --flag-for-wasm-binary
$ cargo wasi run --bin foo
Note: Using
cargo wasi
will printRunning ...
twice, that's normal but only one wasm binary is actually run.
cargo wasi test
Forwards everything to cargo test
, and runs all tests in wasmtime
.
Arguments passed will be forwarded to cargo test
. Note that it's not
necessary to run cargo wasi build
before executing this command. Example
usage looks like:
$ cargo wasi test
$ cargo wasi test my_test_to_run
$ cargo wasi test --lib
$ cargo wasi test --test foo
$ cargo wasi test -- --nocpature
You can find some more info about writing tests in the Rust book's chapter on writing tests.
Note: You'll also want to be sure to consult WASI-specific caveats when testing since there are some gotchas today.
cargo wasi bench
Forwards everything to cargo bench
, and like previous commands also executes
the benchmarks inside of wasmtime
. Arguments passed will be forwarded to
cargo bench
, such as:
$ cargo wasi bench
$ cargo wasi bench my_benchmark_to_run
$ cargo wasi bench --bench foo
$ cargo wasi bench -- --nocpature
cargo wasi fix
Forwards everything to cargo fix
, but again with the --target wasm32-wasi
option which ensures that the fixes are also applied to wasi-specific code (if
any).
cargo wasi version
This subcommand will print out version information about cargo wasi
itself.
This is also known as cargo wasi -V
and cargo wasi --version
.
$ cargo wasi version
$ cargo wasi -V
$ cargo wasi --version
cargo wasi self clean
This is an internal management subcommand for cargo wasi
which completely
clears out the cache that cargo wasi
uses for itself. This cache includes
various metadata files and downloaded versions of tools like wasm-opt
and
wasm-bindgen
.
$ cargo wasi self clean
cargo wasi self update-check
Checks to see if an update is ready for cargo-wasi
. If it is then instructions
to acquire the new update will be printed out.
$ cargo wasi self update-check
Configuration
The cargo wasi
subcomand does not have any CLI flags of its
own but it's still not a one-size-fits-all command, so
configuration needs to go somewhere! The cargo wasi
command supports
TOML-based configuration stored in your
workspace
Cargo.toml
in the [package.metadata]
section:
[package.metadata]
# ...
The keys supported by cargo wasi
are:
[package.metadata]
wasm-opt = true
wasm-name-section = true
wasm-producers-section = true
For more documentation about each key, see its section below.
wasm-opt
This configuration option is a boolean value (true
or false
) which
indicates whether the wasm-opt
optimization tool from the binaryen
toolkit might be executed to further
optimize the produced WebAssembly binaries. The default for this option is
true
.
If this option is set to false
, then wasm-opt
will never be executed.
If this option is set to true
, this does not mean wasm-opt
will
unconditionally run for all builds. A value of true
means that wasm-opt
may run, depending on the internal heuristics of cargo wasi
. For more
information about these heuristics and caveats, see the documentation about
running wasm-opt
.
wasm-name-section
The name
custom
section
records debugging information as names for wasm functions and variables. If you
want reasonable stack traces or debug information it's recommended to have the
name
section present. Builds optimized for size though that have other
channels of debugging may wish to disable this.
This configuration option is a boolean value (true
or false
) which
indicates whether the name
section should be present or not. This option
defaults to true
.
If this option is set to false
then it only takes effect when a build is
produced without debuginfo. For example a cargo wasi build
binary which has
debuginfo would still have the name
section present. A cargo wasi build --release
binary, however, would not have debuginfo and would also have the
name
section removed.
wasm-producers-section
The producers
custom
section
records tools used to produce a WebAssembly module. This is meant for metric
collection in production systems, and is generally harmless to include. Builds
micro-optimized for size, however, may wish to exclude it.
This configuration option is a boolean value (true
or false
) which
indicates whether the producers
section should be present or not. This option
defaults to true
.
If this option is set to false
then it only takes effect when a build is
produced without debuginfo. For example a cargo wasi build
binary which has
debuginfo would still have the producers
section present. A cargo wasi build --release
binary, however, would not have debuginfo and would also have the
producers
section removed.
Running wasm-opt
By default cargo wasi
will run wasm-opt
over optimized WebAssembly
binaries. The wasm-opt
program is a tool in the binaryen
toolkit which is a wasm-to-wasm
transformation that optimizes the input wasm module. Often wasm-opt
can get
10-20% size reductions over LLVM's raw output.
There are a number of heuristics that are used to configure how wasm-opt
is
run though and it's important to keep those in mind!
Which wasm-opt
executed?
Every release of cargo wasi
is hardcoded to download a precompiled version of
wasm-opt
. This binary will be lazily downloaded and then executed. You can
also request that a specific wasm-opt
binary is used via the WASM_OPT
environment variable.
Note that we're interested in feedback on this strategy, so please don't hesitate to file an issue if this doesn't work for you!
Disabled with DWARF debuginfo
If DWARF debug information is requested for a build (default on for debug
builds, default off for release builds) then wasm-opt
will be disabled.
At the time of this writing wasm-opt
does not support preserving DWARF debug
information through its transformations, so wasm-opt
is skipped.
In effect this means that wasm-opt
will not run in debug mode, but it will
run in release mode. If you enable debug info in release mode, though, then it
will not run.
You can configure debuginfo through your Cargo.toml
:
[profile.release]
debug = 1
Selected Optimization Level
The wasm-opt
tool, like most compilers, supports multiple levels of
optimization. The optimization level is by default selected to match rustc
's
own optimization level. If rustc
's optimization level is "0", then wasm-opt
will not be run.
This effectively means that in debug mode this is another reason that
wasm-opt
is disabled (because debug mode uses optimization level 0). In
release mode we will by default execute wasm-opt -O3
because rustc
is
executed with -C opt-level=3
.
You can configure rustc
's and wasm-opt
's optimization level through your
Cargo.toml
. For example to optimize for size instead of speed:
[profile.release]
opt-level = 's'
Disabled via configuration
You can also outright disable wasm-opt
via configuration by
updating your Cargo.toml
:
[package.metadata]
wasm-opt = false
Disabled when wasm-bindgen
is used
Finally, as one last caveat, wasm-opt
is automatically disabled if
wasm-bindgen
is used as part of the build. If wasm-bindgen
is used it's
assumed that WebAssembly Interface Types are also used, and currently
wasm-opt
(at the time of this writing) does not have support for WebAssembly
Interface Types. If we were to run wasm-opt
it would produce a broken binary!
Running wasm-bindgen
Note: Usage of
wasm-bindgen
and WebAssembly Interface Types is highly experimental, it's recommended that you expect breakage and/or surprises if you're using this.
Note: When building your crate with WebAssembly Interface Types enabled via
wasm-bindgen
, due to a bug inwasm-bindgen
, it is currently necessary to build in release mode, i.e.,cargo wasi build --release
.
The wasm-bindgen
project is
primarily targeted at JavaScript and the web, but is also becomimg the primary
experiment grounds of WebAssembly Interface Types for Rust. If you're not using
interface types you probably don't need wasm-bindgen
, but if you're using
interface types read on!
The cargo wasi
subcommand will automatically detect when
wasm-bindgen
-the-crate is used in your dependency graph. When this is seen
then cargo wasi
will download the corresponding precompiled wasm-bindgen
CLI
binary (or cargo install
it) and execute that over the final WebAssembly file.
Currently no configuration for wasm-bindgen
is supported because the support
for WebAssembly Interface Types is unconditionally enabled which takes no
configuration. This aspect of cargo wasi
is highly likely to change and get
improved over time though!
Testing in WASI
Testing in WASI generall works the same as testing in Rust, but there's an important caveat about failing tests in WASI.
The wasm32-wasi
target for Rust is effectively a panic=abort
target which
has no support for unwinding. Most tests report failure, however by panicking!
This means that a failing test will actually abort the whole wasi process, which
isn't always a great experience.
To compound the problems here Rust's test framework by default captures all output of a panic to print later after all tests have finished executing. If the process aborts on a panic though, nothing ends up getting printed! Instead you'll see something like:
$ cargo wasi test
...
Running `/code/wasi-hello-world/target/wasm32-wasi/debug/deps/foo-38c031b0dc9ed5bc.wasm`
running 1 test
test foo ... error: failed to process main module `/code/wasi-hello-world/target/wasm32-wasi/debug/deps/foo-38c031b0dc9ed5bc.wasm`
caused by: Instantiation error: Trap occurred while invoking start function: wasm trap: unreachable, source location: @4143a
and that's not very helpful!
To help with these issues it's recommended to use --nocapture
which will at
least print some information.
$ cargo wasi test -- --nocapture
...
Running `/code/wasi-hello-world/target/wasm32-wasi/debug/deps/foo-38c031b0dc9ed5bc.wasm --nocapture`
running 1 test
test foo ... thread 'main' panicked at 'assertion failed: `(left == right)`
left: `1`,
right: `2`', tests/foo.rs:3:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
error: failed to process main module `/code/wasi-hello-world/target/wasm32-wasi/debug/deps/foo-38c031b0dc9ed5bc.wasm`
caused by: Instantiation error: Trap occurred while invoking start function: wasm trap: unreachable, source location: @4143a
In general testing and wasi isn't great today. It's something we hope to improve over time!
Updating cargo wasi
If you already have cargo-wasi
installed and you'd like to update your
installation, you can execute:
$ cargo install cargo-wasi --force
Uninstalling cargo wasi
If you'd like to remove cargo-wasi
from your system, you'll want to first
clear out the subcommand's caches and then remove the subcommand itself.
$ cargo wasi self clean
$ cargo uninstall cargo-wasi
Contributing to cargo-wasi
This section contains instructions on how to get this project up and running for development. Source code for this project lives on GitHub at https://github.com/bytecodealliance/cargo-wasi.
Prerequisites
-
The
cargo-wasi
subcommand is written in Rust, so you'll want Rust installed -
Running tests requires
wasmtime
is installed and in$PATH
or an existing runtime provided viaCARGO_TARGET_WASM32_WASI_RUNNER
.
Getting the code
You'll clone the code via git
:
$ git clone https://github.com/bytecodealliance/cargo-wasi
Testing changes
We'd like tests ideally to be written for all changes. Test can be run via:
$ cargo test
You'll be adding tests primarily to tests/tests/*.rs
.
Submitting changes
Changes to cargo-wasi
are managed through Pull Requests, and anyone is
more than welcome to submit a pull request! We'll try to get to reviewing it or
responding to it in at most a few days.
Code Formatting
Code is required to be formatted with the current Rust stable's cargo fmt
command. This is checked on CI.
Continuous Integration
The CI for the cargo-wasi
repository is relatively significant. It tests
changes on Windows, macOS, and Linux. It also performs a "dry run" of the
release process to ensure that release binaries can be built and are ready to be
published.
Publishing a New Version
Publication of this crate is entirely automated via CI. A publish happens
whenever a tag is pushed to the repository, so to publish a new version you'll
want to make a PR that bumps the version numbers (see the bump.rs
scripts in
the root of the repository), merge the PR, then tag the PR and push the tag.
That should trigger all that's necessary to publish all the crates and binaries
to crates.io.