1# Building a Minimal Wasmtime embedding 2 3Wasmtime embeddings may wish to optimize for binary size and runtime footprint 4to fit on a small system. This documentation is intended to guide some features 5of Wasmtime and how to best produce a minimal build of Wasmtime. 6 7## Building a minimal CLI 8 9> *Note*: the exact numbers in this section were last updated on 2023-10-18 on a 10> macOS aarch64 host. For up-to-date numbers consult the artifacts in the [`dev` 11> release of Wasmtime][dev] where the `wasmtime-min` executable represents the 12> culmination of these steps. 13 14[dev]: https://github.com/bytecodealliance/wasmtime/releases/tag/dev 15 16Many Wasmtime embeddings go through the `wasmtime` crate as opposed to the 17`wasmtime` CLI executable, but to start out let's take a look at minimizing the 18command line executable. By default the wasmtime command line executable is 19relatively large: 20 21```shell 22$ cargo build 23$ ls -l ./target/debug/wasmtime 24-rwxr-xr-x@ 1 root root 140M Oct 18 08:33 target/debug/wasmtime 25``` 26 27The easiest size optimization is to compile with optimizations. This will strip 28lots of dead code and additionally generate much less debug information by 29default 30 31```shell 32$ cargo build --release 33$ ls -l ./target/release/wasmtime 34-rwxr-xr-x@ 1 root root 33M Oct 18 08:34 target/release/wasmtime 35``` 36 37Much better, but still relatively large! The next thing that can be done is to 38disable the default features of the `wasmtime-cli` crate. This will remove all 39optional functionality from the crate and strip it down to the bare bones 40functionality. Note though that `run` is included to keep the ability to run 41precompiled WebAssembly files as otherwise the CLI doesn't have any 42functionality which isn't too useful. 43 44```shell 45$ cargo build --release --no-default-features --features run 46$ ls -l ./target/release/wasmtime 47-rwxr-xr-x@ 1 root root 6.7M Oct 18 08:37 target/release/wasmtime 48``` 49 50Note that this executable is stripped to the bare minimum of functionality which 51notably means it does not have a compiler for WebAssembly files. This means that 52`wasmtime compile` is no longer supported meaning that `*.cwasm` files must be 53fed to `wasmtime run` to execute files. Additionally error messages will be 54worse in this mode as less contextual information is provided. 55 56The final Wasmtime-specific optimization you can apply is to disable logging 57statements. Wasmtime and its dependencies make use of the [`log` 58crate](https://docs.rs/log) and [`tracing` crate](https://docs.rs/tracing) for 59debugging and diagnosing. For a minimal build this isn't needed though so this 60can all be disabled through Cargo features to shave off a small amount of code. 61Note that for custom embeddings you'd need to replicate the `disable-logging` 62feature which sets the `max_level_off` feature for the `log` and `tracing` 63crate. 64 65```shell 66$ cargo build --release --no-default-features --features run,disable-logging 67$ ls -l ./target/release/wasmtime 68-rwxr-xr-x@ 1 root root 6.7M Oct 18 08:37 target/release/wasmtime 69``` 70 71At this point the next line of tricks to apply to minimize binary size are 72[general tricks-of-the-trade for Rust 73programs](https://github.com/johnthagen/min-sized-rust) and are no longer 74specific to Wasmtime. For example the first thing that can be done is to 75optimize for size rather than speed via rustc's `s` optimization level. 76This uses Cargo's [environment-variable based configuration][cargo-env-config] 77via the `CARGO_PROFILE_RELEASE_OPT_LEVEL=s` environment variable to configure 78this. 79 80[cargo-env-config]: https://doc.rust-lang.org/cargo/reference/config.html#profile 81 82```shell 83$ export CARGO_PROFILE_RELEASE_OPT_LEVEL=s 84$ cargo build --release --no-default-features --features run,disable-logging 85$ ls -l ./target/release/wasmtime 86-rwxr-xr-x@ 1 root root 6.8M Oct 18 08:40 target/release/wasmtime 87``` 88 89Note that the size has increased here slightly instead of going down. Optimizing 90for speed-vs-size can affect a number of heuristics in LLVM so it's best to test 91out locally what's best for your embedding. Further examples below continue to 92pass this flag since by the end it will produce a smaller binary than the 93default optimization level of "3" for release mode. You may wish to also try an 94optimization level of "2" and see which produces a smaller build for you. 95 96After optimizations levels the next compilation setting to configure is 97Rust's "panic=abort" mode where panics translate to process aborts rather than 98unwinding. This removes landing pads from code as well as unwind tables from the 99executable. 100 101```shell 102$ export CARGO_PROFILE_RELEASE_OPT_LEVEL=s 103$ export CARGO_PROFILE_RELEASE_PANIC=abort 104$ cargo build --release --no-default-features --features run,disable-logging 105$ ls -l ./target/release/wasmtime 106-rwxr-xr-x@ 1 root root 5.0M Oct 18 08:40 target/release/wasmtime 107``` 108 109Next, if the compile time hit is acceptable, LTO can be enabled to provide 110deeper opportunities for compiler optimizations to remove dead code and 111deduplicate. Do note that this will take a significantly longer amount of time 112to compile than previously. Here LTO is configured with 113`CARGO_PROFILE_RELEASE_LTO=true`. 114 115```shell 116$ export CARGO_PROFILE_RELEASE_OPT_LEVEL=s 117$ export CARGO_PROFILE_RELEASE_PANIC=abort 118$ export CARGO_PROFILE_RELEASE_LTO=true 119$ cargo build --release --no-default-features --features run,disable-logging 120$ ls -l ./target/release/wasmtime 121-rwxr-xr-x@ 1 root root 3.3M Oct 18 08:42 target/release/wasmtime 122``` 123 124Similar to LTO above rustc can be further instructed to place all crates into 125their own single object file instead of multiple by default. This again 126increases compile times. Here that's done with 127`CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1`. 128 129```shell 130$ export CARGO_PROFILE_RELEASE_OPT_LEVEL=s 131$ export CARGO_PROFILE_RELEASE_PANIC=abort 132$ export CARGO_PROFILE_RELEASE_LTO=true 133$ export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1 134$ cargo build --release --no-default-features --features run,disable-logging 135$ ls -l ./target/release/wasmtime 136-rwxr-xr-x@ 1 root root 3.3M Oct 18 08:43 target/release/wasmtime 137``` 138 139Note that with LTO using a single codegen unit may only have marginal benefit. 140If not using LTO, however, a single codegen unit will likely provide benefit 141over the default 16 codegen units. 142 143One final flag before getting to nightly features is to strip debug information 144from the standard library. In `--release` mode Cargo by default doesn't generate 145debug information for local crates, but the Rust standard library may have debug 146information still included with it. This is configured via 147`CARGO_PROFILE_RELEASE_STRIP=debuginfo` 148 149```shell 150$ export CARGO_PROFILE_RELEASE_OPT_LEVEL=s 151$ export CARGO_PROFILE_RELEASE_PANIC=abort 152$ export CARGO_PROFILE_RELEASE_LTO=true 153$ export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1 154$ export CARGO_PROFILE_RELEASE_STRIP=debuginfo 155$ cargo build --release --no-default-features --features run,disable-logging 156$ ls -l ./target/release/wasmtime 157-rwxr-xr-x@ 1 root root 2.4M Oct 18 08:44 target/release/wasmtime 158``` 159 160Next, if your use case allows it, the Nightly Rust toolchain provides a number 161of other options to minimize the size of binaries. Note the usage of `+nightly` here 162to the `cargo` command to use a Nightly toolchain (assuming your local toolchain 163is installed with rustup). Also note that due to the nature of nightly the exact 164flags here may not work in the future. Please open an issue with Wasmtime if 165these commands don't work and we'll update the documentation. 166 167The first nightly feature we can leverage is to remove filename and line number 168information in panics with `-Zlocation-detail=none` 169 170```shell 171$ export CARGO_PROFILE_RELEASE_OPT_LEVEL=s 172$ export CARGO_PROFILE_RELEASE_PANIC=abort 173$ export CARGO_PROFILE_RELEASE_LTO=true 174$ export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1 175$ export CARGO_PROFILE_RELEASE_STRIP=debuginfo 176$ export RUSTFLAGS="-Zlocation-detail=none" 177$ cargo +nightly build --release --no-default-features --features run,disable-logging 178$ ls -l ./target/release/wasmtime 179-rwxr-xr-x@ 1 root root 2.4M Oct 18 08:43 target/release/wasmtime 180``` 181 182Further along the line of nightly features the next optimization will recompile 183the standard library without unwinding information, trimming out a bit more from 184the standard library. This uses the `-Zbuild-std` flag to Cargo. Note that this 185additionally requires `--target` as well which will need to be configured for 186your particular platform. 187 188```shell 189$ export CARGO_PROFILE_RELEASE_OPT_LEVEL=s 190$ export CARGO_PROFILE_RELEASE_PANIC=abort 191$ export CARGO_PROFILE_RELEASE_LTO=true 192$ export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1 193$ export CARGO_PROFILE_RELEASE_STRIP=debuginfo 194$ export RUSTFLAGS="-Zlocation-detail=none" 195$ cargo +nightly build --release --no-default-features --features run,disable-logging \ 196 -Z build-std=std,panic_abort --target aarch64-apple-darwin 197$ ls -l ./target/aarch64-apple-darwin/release/wasmtime 198-rwxr-xr-x@ 1 root root 2.3M Oct 18 09:39 target/aarch64-apple-darwin/release/wasmtime 199``` 200 201Next the Rust standard library has some optional features in addition to 202Wasmtime, such as printing of backtraces. This may not be required in minimal 203environments so the features of the standard library can be disabled with the 204`-Zbuild-std-features=` flag which configures the set of enabled features to be 205empty. 206 207```shell 208$ export CARGO_PROFILE_RELEASE_OPT_LEVEL=s 209$ export CARGO_PROFILE_RELEASE_PANIC=abort 210$ export CARGO_PROFILE_RELEASE_LTO=true 211$ export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1 212$ export CARGO_PROFILE_RELEASE_STRIP=debuginfo 213$ export RUSTFLAGS="-Zlocation-detail=none" 214$ cargo +nightly build --release --no-default-features --features run,disable-logging \ 215 -Z build-std=std,panic_abort --target aarch64-apple-darwin \ 216 -Z build-std-features= 217$ ls -l ./target/aarch64-apple-darwin/release/wasmtime 218-rwxr-xr-x@ 1 root root 2.1M Oct 18 09:39 target/aarch64-apple-darwin/release/wasmtime 219``` 220 221## Minimizing further 222 223Above shows an example of taking the default `cargo build` result of 130M down 224to a 2.1M binary for the `wasmtime` executable. Similar steps can be done to 225reduce the size of the C API binary artifact as well which currently produces a 226~2.8M dynamic library. This is currently the smallest size with the source code 227as-is, but there are more size reductions which haven't been implemented yet. 228 229This is a listing of some example sources of binary size. Some sources of binary 230size may not apply to custom embeddings since, for example, your custom 231embedding might already not use WASI and might already not be included. 232 233* WASI in the Wasmtime CLI - currently the CLI includes all of WASI. This 234 includes two separate implementations of WASI - one for preview2 and one for 235 preview1. This accounts for 1M+ of space which is a significant chunk of the 236 remaining 2.1M. While removing just preview2 or preview1 would be easy enough 237 with a Cargo feature, the resulting executable wouldn't be able to do 238 anything. Something like a [plugin feature for the 239 CLI](https://github.com/bytecodealliance/wasmtime/issues/7348), however, would 240 enable removing WASI while still being a usable executable. 241 242* Argument parsing in the Wasmtime CLI - as a command line executable `wasmtime` 243 contains parsing of command line arguments which currently uses the `clap` 244 crate. This contributes ~200k of binary size to the final executable which 245 would likely not be present in a custom embedding of Wasmtime. While this 246 can't be removed from Wasmtime it's something to consider when evaluating the 247 size of CI artifacts. 248 249* Cranelift in the C API - one of the features of Wasmtime is the ability to 250 have a runtime without Cranelift that only supports precompiled (AOT) wasm 251 modules. It's [not possible to build the C API without 252 Cranelift](https://github.com/bytecodealliance/wasmtime/issues/7349) though 253 because defining host functions requires Cranelift at this time to emit some 254 stubs. This means that the C API is significantly larger than a custom Rust 255 embedding which doesn't suffer from the same restriction. This means that 256 while it's still possible to build an embedding of Wasmtime which doesn't have 257 Cranelift it's not easy to see what it might look like size-wise from 258 looking at the C API artifacts. 259 260* Formatting strings in Wasmtime - Wasmtime makes extensive use of formatting 261 strings for error messages and other purposes throughout the implementation. 262 Most of this is intended for debugging and understanding more when something 263 goes wrong, but much of this is not necessary for a truly minimal embedding. 264 In theory much of this could be conditionally compiled out of the Wasmtime 265 project to produce a smaller executable. Just how much of the final binary 266 size is accounted for by formatting string is unknown, but it's well known in 267 Rust that `std::fmt` is not the slimmest of modules. 268 269* Cranelift vs Winch - the "min" builds on CI try to exclude Cranelift from 270 their binary footprint (e.g. the CLI excludes it) but this comes at a cost of 271 the final executable not supporting compilation of wasm modules. If this is 272 required then no effort has yet been put into minimizing the code size of 273 Cranelift itself. One possible tradeoff that can be made though is to choose 274 between the Winch baseline compiler vs Cranelift. Winch should be much smaller 275 from a compiled footprint point of view while not sacrificing everything in 276 terms of performance. Note though that Winch is still under development. 277 278Above are some future avenues to take in terms of reducing the binary size of 279Wasmtime and various tradeoffs that can be made. The Wasmtime project is eager 280to hear embedder use cases/profiles if Wasmtime is not suitable for binary size 281reasons today. Please feel free to [open an 282issue](https://github.com/bytecodealliance/wasmtime/issues/new) and let us know 283and we'd be happy to discuss more how best to handle a particular use case. 284 285# Building Wasmtime for a Custom Platform 286 287If you're not running on a built-in supported platform such as Windows, macOS, 288or Linux, then Wasmtime won't work out-of-the-box for you. Wasmtime includes a 289compilation mode, however, that enables you to define how to work with the 290platform externally. 291 292This mode is enabled when `--cfg wasmtime_custom_platform` is passed to rustc, 293via `RUSTFLAGS` for example when building through Cargo, when an existing 294platform is not matched. This means that with this configuration Wasmtime may be 295compiled for custom or previously unknown targets. 296 297Wasmtime's current "platform embedding API" which is required to operate is 298defined at `examples/min-platform/embedding/wasmtime-platform.h`. That directory 299additionally has an example of building a minimal `*.so` on Linux which has the 300platform API implemented in C using Linux syscalls. While a bit contrived it 301effectively shows a minimal Wasmtime embedding which has no dependencies other 302than the platform API. 303 304Building Wasmtime for a custom platform is not a turnkey process right now, 305there are a number of points that need to be considered: 306 307* For a truly custom platform you'll probably want to create a [custom Rust 308 target](https://docs.rust-embedded.org/embedonomicon/custom-target.html). This 309 means that Nightly Rust will be required. 310 311* Wasmtime and its dependencies require the Rust standard library `std` to be 312 available. The Rust standard library can be compiled for any target with 313 unsupported functionality being stubbed out. This mode of compiling the Rust 314 standard library is not stable, however. Currently this is done through the 315 `-Zbuild-std` argument to Cargo along with a 316 `+RUSTC_BOOTSTRAP_SYNTHETIC_TARGET=1` environment variable. 317 318* Wasmtime additionally depends on the availability of a memory allocator (e.g. 319 `malloc`). Wasmtime assumes that failed memory allocation aborts the process. 320 321* Not all features for Wasmtime can be built for custom targets. For example 322 WASI support does not work on custom targets. When building Wasmtime you'll 323 probably want `--no-default-features` and will then want to incrementally add 324 features back in as needed. 325 326The `examples/min-platform` directory has an example of building this minimal 327embedding and some necessary steps. Combined with the above features about 328producing a minimal build currently produces a 400K library on Linux. 329