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