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 2024-12-12 on a 10> Linux x86\_64 host. For up-to-date numbers consult the artifacts in the [`dev` 11> release of Wasmtime][dev] where the `min/lib/libwasmtime.so` binary 12> represents the 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 17Wasmtime C API `libwasmtime.so`, but to start out let's take a look at 18minimizing the dynamic library as a case study. By default the C API is 19relatively large: 20 21```shell-session 22$ cargo build -p wasmtime-c-api 23$ ls -lh ./target/debug/libwasmtime.so 24-rwxrwxr-x 2 alex alex 260M Dec 12 07:46 target/debug/libwasmtime.so 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-session 32$ cargo build -p wasmtime-c-api --release 33$ ls -lh ./target/release/libwasmtime.so 34-rwxrwxr-x 2 alex alex 19M Dec 12 07:46 target/release/libwasmtime.so 35``` 36 37Much better, but still relatively large! The next thing that can be done is to 38disable the default features of the C API. This will remove all 39optional functionality from the crate and strip it down to the bare bones 40functionality. 41 42```shell-session 43$ cargo build -p wasmtime-c-api --release --no-default-features 44$ ls -lh ./target/release/libwasmtime.so 45-rwxrwxr-x 2 alex alex 2.1M Dec 12 07:47 target/release/libwasmtime.so 46``` 47 48Note that this library is stripped to the bare minimum of functionality which 49notably means it does not have a compiler for WebAssembly files. This means that 50compilation is no longer supported meaning that `*.cwasm` files must used to 51create a module. Additionally error messages will be worse in this mode as less 52contextual information is provided. 53 54The final Wasmtime-specific optimization you can apply is to disable logging 55statements. Wasmtime and its dependencies make use of the [`log` 56crate](https://docs.rs/log) and [`tracing` crate](https://docs.rs/tracing) for 57debugging and diagnosing. For a minimal build this isn't needed though so this 58can all be disabled through Cargo features to shave off a small amount of code. 59Note that for custom embeddings you'd need to replicate the `disable-logging` 60feature which sets the `max_level_off` feature for the `log` and `tracing` 61crate. 62 63```shell-session 64$ cargo build -p wasmtime-c-api --release --no-default-features --features disable-logging 65$ ls -lh ./target/release/libwasmtime.so 66-rwxrwxr-x 2 alex alex 2.1M Dec 12 07:49 target/release/libwasmtime.so 67``` 68 69At this point the next line of tricks to apply to minimize binary size are 70[general tricks-of-the-trade for Rust 71programs](https://github.com/johnthagen/min-sized-rust) and are no longer 72specific to Wasmtime. For example the first thing that can be done is to 73optimize for size rather than speed via rustc's `s` optimization level. 74This uses Cargo's [environment-variable based configuration][cargo-env-config] 75via the `CARGO_PROFILE_RELEASE_OPT_LEVEL=s` environment variable to configure 76this. 77 78[cargo-env-config]: https://doc.rust-lang.org/cargo/reference/config.html#profile 79 80```shell-session 81$ export CARGO_PROFILE_RELEASE_OPT_LEVEL=s 82$ cargo build -p wasmtime-c-api --release --no-default-features --features disable-logging 83$ ls -lh ./target/release/libwasmtime.so 84-rwxrwxr-x 2 alex alex 2.4M Dec 12 07:49 target/release/libwasmtime.so 85``` 86 87Note that the size has increased here slightly instead of going down. Optimizing 88for speed-vs-size can affect a number of heuristics in LLVM so it's best to test 89out locally what's best for your embedding. Further examples below continue to 90pass this flag since by the end it will produce a smaller binary than the 91default optimization level of "3" for release mode. You may wish to also try an 92optimization level of "2" and see which produces a smaller build for you. 93 94After optimizations levels the next compilation setting to configure is 95Rust's "panic=abort" mode where panics translate to process aborts rather than 96unwinding. This removes landing pads from code as well as unwind tables from the 97executable. 98 99```shell-session 100$ export CARGO_PROFILE_RELEASE_OPT_LEVEL=s 101$ export CARGO_PROFILE_RELEASE_PANIC=abort 102$ cargo build -p wasmtime-c-api --release --no-default-features --features disable-logging 103$ ls -lh ./target/release/libwasmtime.so 104-rwxrwxr-x 2 alex alex 2.0M Dec 12 07:49 target/release/libwasmtime.so 105``` 106 107Next, if the compile time hit is acceptable, LTO can be enabled to provide 108deeper opportunities for compiler optimizations to remove dead code and 109deduplicate. Do note that this will take a significantly longer amount of time 110to compile than previously. Here LTO is configured with 111`CARGO_PROFILE_RELEASE_LTO=true`. 112 113```shell-session 114$ export CARGO_PROFILE_RELEASE_OPT_LEVEL=s 115$ export CARGO_PROFILE_RELEASE_PANIC=abort 116$ export CARGO_PROFILE_RELEASE_LTO=true 117$ cargo build -p wasmtime-c-api --release --no-default-features --features disable-logging 118$ ls -lh ./target/release/libwasmtime.so 119-rwxrwxr-x 2 alex alex 1.2M Dec 12 07:50 target/release/libwasmtime.so 120``` 121 122Similar to LTO above rustc can be further instructed to place all crates into 123their own single object file instead of multiple by default. This again 124increases compile times. Here that's done with 125`CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1`. 126 127```shell-session 128$ export CARGO_PROFILE_RELEASE_OPT_LEVEL=s 129$ export CARGO_PROFILE_RELEASE_PANIC=abort 130$ export CARGO_PROFILE_RELEASE_LTO=true 131$ export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1 132$ cargo build -p wasmtime-c-api --release --no-default-features --features disable-logging 133$ ls -lh ./target/release/libwasmtime.so 134-rwxrwxr-x 2 alex alex 1.2M Dec 12 07:50 target/release/libwasmtime.so 135``` 136 137Note that with LTO using a single codegen unit may only have marginal benefit. 138If not using LTO, however, a single codegen unit will likely provide benefit 139over the default 16 codegen units. 140 141One final flag before getting to nightly features is to strip debug information 142from the standard library. In `--release` mode Cargo by default doesn't generate 143debug information for local crates, but the Rust standard library may have debug 144information still included with it. This is configured via 145`CARGO_PROFILE_RELEASE_STRIP=debuginfo` 146 147```shell-session 148$ export CARGO_PROFILE_RELEASE_OPT_LEVEL=s 149$ export CARGO_PROFILE_RELEASE_PANIC=abort 150$ export CARGO_PROFILE_RELEASE_LTO=true 151$ export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1 152$ export CARGO_PROFILE_RELEASE_STRIP=debuginfo 153$ cargo build -p wasmtime-c-api --release --no-default-features --features disable-logging 154$ ls -lh ./target/release/libwasmtime.so 155-rwxrwxr-x 2 alex alex 1.2M Dec 12 07:50 target/release/libwasmtime.so 156``` 157 158Next, if your use case allows it, the Nightly Rust toolchain provides a number 159of other options to minimize the size of binaries. Note the usage of `+nightly` here 160to the `cargo` command to use a Nightly toolchain (assuming your local toolchain 161is installed with rustup). Also note that due to the nature of nightly the exact 162flags here may not work in the future. Please open an issue with Wasmtime if 163these commands don't work and we'll update the documentation. 164 165The first nightly feature we can leverage is to remove filename and line number 166information in panics with `-Zlocation-detail=none` 167 168```shell-session 169$ export CARGO_PROFILE_RELEASE_OPT_LEVEL=s 170$ export CARGO_PROFILE_RELEASE_PANIC=abort 171$ export CARGO_PROFILE_RELEASE_LTO=true 172$ export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1 173$ export CARGO_PROFILE_RELEASE_STRIP=debuginfo 174$ export RUSTFLAGS="-Zlocation-detail=none" 175$ cargo +nightly build -p wasmtime-c-api --release --no-default-features --features disable-logging 176$ ls -lh ./target/release/libwasmtime.so 177-rwxrwxr-x 2 alex alex 1.2M Dec 12 07:51 target/release/libwasmtime.so 178``` 179 180Further along the line of nightly features the next optimization will recompile 181the standard library without unwinding information, trimming out a bit more from 182the standard library. This uses the `-Zbuild-std` flag to Cargo. Note that this 183additionally requires `--target` as well which will need to be configured for 184your particular platform. 185 186```shell-session 187$ export CARGO_PROFILE_RELEASE_OPT_LEVEL=s 188$ export CARGO_PROFILE_RELEASE_PANIC=abort 189$ export CARGO_PROFILE_RELEASE_LTO=true 190$ export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1 191$ export CARGO_PROFILE_RELEASE_STRIP=debuginfo 192$ export RUSTFLAGS="-Zlocation-detail=none" 193$ cargo +nightly build -p wasmtime-c-api --release --no-default-features --features disable-logging \ 194 -Z build-std=std,panic_abort --target x86_64-unknown-linux-gnu 195$ ls -lh target/x86_64-unknown-linux-gnu/release/libwasmtime.so 196-rwxrwxr-x 2 alex alex 941K Dec 12 07:52 target/x86_64-unknown-linux-gnu/release/libwasmtime.so 197``` 198 199Next the Rust standard library has some optional features in addition to 200Wasmtime, such as printing of backtraces. This may not be required in minimal 201environments so the features of the standard library can be disabled with the 202`-Zbuild-std-features=` flag which configures the set of enabled features to be 203empty. 204 205```shell-session 206$ export CARGO_PROFILE_RELEASE_OPT_LEVEL=s 207$ export CARGO_PROFILE_RELEASE_PANIC=abort 208$ export CARGO_PROFILE_RELEASE_LTO=true 209$ export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1 210$ export CARGO_PROFILE_RELEASE_STRIP=debuginfo 211$ export RUSTFLAGS="-Zlocation-detail=none" 212$ cargo +nightly build -p wasmtime-c-api --release --no-default-features --features disable-logging \ 213 -Z build-std=std,panic_abort --target x86_64-unknown-linux-gnu \ 214 -Z build-std-features= 215$ ls -lh target/x86_64-unknown-linux-gnu/release/libwasmtime.so 216-rwxrwxr-x 2 alex alex 784K Dec 12 07:53 target/x86_64-unknown-linux-gnu/release/libwasmtime.so 217``` 218 219And finally, if you can enable the `panic_immediate_abort` feature of the Rust 220standard library to shrink panics even further. Note that this comes at a cost 221of making bugs/panics very difficult to debug. 222 223```shell-session 224$ export CARGO_PROFILE_RELEASE_OPT_LEVEL=s 225$ export CARGO_PROFILE_RELEASE_PANIC=abort 226$ export CARGO_PROFILE_RELEASE_LTO=true 227$ export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1 228$ export CARGO_PROFILE_RELEASE_STRIP=debuginfo 229$ export RUSTFLAGS="-Zlocation-detail=none" 230$ cargo +nightly build -p wasmtime-c-api --release --no-default-features --features disable-logging \ 231 -Z build-std=std,panic_abort --target x86_64-unknown-linux-gnu \ 232 -Z build-std-features=panic_immediate_abort 233$ ls -lh target/x86_64-unknown-linux-gnu/release/libwasmtime.so 234-rwxrwxr-x 2 alex alex 698K Dec 12 07:54 target/x86_64-unknown-linux-gnu/release/libwasmtime.so 235``` 236 237## Minimizing further 238 239Above shows an example of taking the default `cargo build` result of 260M down 240to a 700K binary for the `libwasmtime.so` binary of the C API. Similar steps 241can be done to reduce the size of the `wasmtime` CLI executable as well. This is 242currently the smallest size with the source code as-is, but there are more size 243reductions which haven't been implemented yet. 244 245This is a listing of some example sources of binary size. Some sources of binary 246size may not apply to custom embeddings since, for example, your custom 247embedding might already not use WASI and might already not be included. 248 249* Unused functionality in the C API - building `libwasmtime.{a,so}` can show a 250 misleading file size because the linker is unable to remove unused code. For 251 example `libwasmtime.so` contains all code for the C API but your embedding 252 may not be using all of the symbols present so in practice the final linked 253 binary will often be much smaller than `libwasmtime.so`. Similarly 254 `libwasmtime.a` is forced to contain the entire C API so its size is likely 255 much larger than a linked application. For a minimal embedding it's 256 recommended to link against `libwasmtime.a` with `--gc-sections` as a linker 257 flag and evaluate the size of your own application. 258 259* Formatting strings in Wasmtime - Wasmtime makes extensive use of formatting 260 strings for error messages and other purposes throughout the implementation. 261 Most of this is intended for debugging and understanding more when something 262 goes wrong, but much of this is not necessary for a truly minimal embedding. 263 In theory much of this could be conditionally compiled out of the Wasmtime 264 project to produce a smaller executable. Just how much of the final binary 265 size is accounted for by formatting string is unknown, but it's well known in 266 Rust that `std::fmt` is not the slimmest of modules. 267 268* CLI: WASI implementation - currently the CLI includes all of WASI. This 269 includes two separate implementations of WASI - one for preview2 and one for 270 preview1. This accounts for 1M+ of space which is a significant chunk of the 271 remaining ~2M. While removing just preview2 or preview1 would be easy enough 272 with a Cargo feature, the resulting executable wouldn't be able to do 273 anything. Something like a [plugin feature for the 274 CLI](https://github.com/bytecodealliance/wasmtime/issues/7348), however, would 275 enable removing WASI while still being a usable executable. Note that the C 276 API's implementation of WASI can be disabled because custom host functionality 277 can be provided. 278 279* CLI: Argument parsing - as a command line executable `wasmtime` contains 280 parsing of command line arguments which currently uses the `clap` crate. This 281 contributes ~200k of binary size to the final executable which would likely 282 not be present in a custom embedding of Wasmtime. While this can't be removed 283 from Wasmtime it's something to consider when evaluating the size of CI 284 artifacts. 285 286* Cranelift vs Winch - the "min" builds on CI exclude Cranelift from their 287 binary footprint but this comes at a cost of the final binary not 288 supporting compilation of wasm modules. If this is required then no effort 289 has yet been put into minimizing the code size of Cranelift itself. One 290 possible tradeoff that can be made though is to choose between the Winch 291 baseline compiler vs Cranelift. Winch should be much smaller from a compiled 292 footprint point of view while not sacrificing everything in terms of 293 performance. Note though that Winch is still under development. 294 295Above are some future avenues to take in terms of reducing the binary size of 296Wasmtime and various tradeoffs that can be made. The Wasmtime project is eager 297to hear embedder use cases/profiles if Wasmtime is not suitable for binary size 298reasons today. Please feel free to [open an 299issue](https://github.com/bytecodealliance/wasmtime/issues/new) and let us know 300and we'd be happy to discuss more how best to handle a particular use case. 301 302# Building Wasmtime for a Custom Platform 303 304Wasmtime supports a wide range of functionality by default on major operating 305systems such as Windows, macOS, and Linux, but this functionality is not 306necessarily present on all platforms (much less custom platforms). Most of 307Wasmtime's features are gated behind either platform-specific configuration 308flags or Cargo feature flags. The `wasmtime` crate for example documents 309[important crate 310features](https://docs.rs/wasmtime/latest/wasmtime/#crate-features) which likely 311want to be disabled for custom platforms. 312 313Not all of Wasmtime's features are supported on all platforms, but many are 314enabled by default. For example the `parallel-compilation` crate feature 315requires the host platform to have threads, or in other words the Rust `rayon` 316crate must compile for your platform. If the `parallel-compilation` feature is 317disabled, though, then `rayon` won't be compiled. For a custom platform, one of 318the first things you'll want to do is to disable the default features of the 319`wasmtime` crate (or C API). 320 321Some important features to be aware of for custom platforms are: 322 323* `runtime` - you likely want to enable this feature since this includes the 324 runtime to actually execute WebAssembly binaries. 325 326* `cranelift` and `winch` - you likely want to disable these features. This 327 primarily cuts down on binary size. Note that you'll need to use `*.cwasm` 328 artifacts so wasm files will need to be compiled outside of the target 329 platform and transferred to them. 330 331* `signals-based-traps` - without this feature Wasmtime won't rely on host OS 332 signals (e.g. segfaults) at runtime and will instead perform manual checks to 333 avoid signals. This increases portability at the cost of runtime performance. 334 For maximal portability leave this disabled. 335 336When compiling Wasmtime for an unknown platform, for example "not Windows" or 337"not Unix", then Wasmtime will need some symbols to be provided by the embedder 338to operate correctly. The header file at 339[`examples/min-platform/embedding/wasmtime-platform.h`][header] describes the 340symbols that the Wasmtime runtime requires to work which your platform will need 341to provide. Some important notes about this are: 342 343* `wasmtime_tls_{get,set}` are required for the runtime to operate. Effectively 344 a single pointer of TLS storage is necessary. Whether or not this is actually 345 stored in TLS is up to the embedder, for example [storage in `static` 346 memory][tls] is ok if the embedder knows it won't be using threads. 347 348* `WASMTIME_SIGNALS_BASED_TRAPS` - if this `#define` is given (e.g. the 349 `signals-based-traps` feature was enabled at compile time), then your platform 350 must have the concept of virtual memory and support `mmap`-like APIs and 351 signal handling. Many APIs in [this header][header] are disabled if 352 `WASMTIME_SIGNALS_BASED_TRAPS` is turned off which is why it's more portable, 353 but if you enable this feature all of these APIs must be implemented. 354 355You can find an example [in the `wasmtime` repository][example] of building a 356minimal embedding. Note that for Rust code you'll be using `#![no_std]` and 357you'll need to provide a memory allocator and a panic handler as well. The 358memory alloator will likely get hooked up to your platform's memory allocator 359and the panic handler mostly just needs to abort. 360 361Building Wasmtime for a custom platform is not a turnkey process right now, 362there are a number of points that need to be considered: 363 364* For a truly custom platform you'll probably want to create a [custom Rust 365 target](https://docs.rust-embedded.org/embedonomicon/custom-target.html). This 366 means that Nightly Rust will be required. 367 368* Wasmtime depends on the availability of a memory allocator (e.g. `malloc`). 369 Wasmtime assumes that failed memory allocation aborts execution (except for 370 the case of allocating linear memories and growing them). 371 372* Not all features for Wasmtime can be built for custom targets. For example 373 WASI support does not work on custom targets. When building Wasmtime you'll 374 probably want `--no-default-features` and will then want to incrementally add 375 features back in as needed. 376 377The `examples/min-platform` directory has an example of building this minimal 378embedding and some necessary steps. Combined with the above features about 379producing a minimal build currently produces a 400K library on Linux. 380 381[header]: https://github.com/bytecodealliance/wasmtime/blob/main/examples/min-platform/embedding/wasmtime-platform.h 382[tls]: https://github.com/bytecodealliance/wasmtime/blob/e1307216f2aa74fd60c621c8fa326ba80e2a2f75/examples/min-platform/embedding/wasmtime-platform.c#L144-L150 383[example]: https://github.com/bytecodealliance/wasmtime/blob/main/examples/min-platform/README.md 384