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