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