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