1# Conditional Compilation in Wasmtime
2
3The `wasmtime` crate and workspace is both quite configurable in terms of
4runtime configuration (e.g. `Config::*`) and compile-time configuration (Cargo
5features). Wasmtime also wants to take advantage of native hardware features on
6specific CPUs and operating systems to implement optimizations for executing
7WebAssembly. This overall leads to the state where the source code for Wasmtime
8has quite a lot of `#[cfg]` directives and is trying to wrangle the
9combinatorial explosion of:
10
111. All possible CPU architectures that Wasmtime (or Rust) supports.
122. All possible operating systems that Wasmtime (or Rust) supports.
133. All possible feature combinations of the `wasmtime` crate.
14
15Like any open source project one of the goals of Wasmtime is to have readable
16and understandable code and to that effect we ideally don't have `#[cfg]`
17everywhere throughout the codebase in confusing combinations. The goal of this
18document is to explain the various guidelines we have for conditional
19compilation in Rust and some recommended styles for working with `#[cfg]` in a
20maintainable and scalable manner.
21
22## Rust's `#[cfg]` attribute
23
24If you haven't worked with Rust much before or if you'd like a refresher, Rust's
25main ability to handle conditional compilation is the `#[cfg]` attribute. This
26is semantically and structurally different from `#ifdef` in C/C++ and gives rise
27to alternative patterns which look quite different as well.
28
29One of the more common conditional compilation attributes in Rust is
30`#[cfg(test)]` which enables including a module or a piece of code only when
31compiled with `cargo test` (or `rustc`'s `--test` flag). There are many other
32directives you can put in `#[cfg]`, however, for example:
33
34* `#[cfg(target_arch = "...")]` - this can be used to detect the architecture
35  that the code was compiled for.
36* `#[cfg(target_os = "...")]` - this can be used to detect the operating system
37  that the code was compiled for.
38* `#[cfg(feature = "...")]` - these correspond to [Cargo features][cargo] and
39  are enabled when depending on crates in `Cargo.toml`.
40* `#[cfg(has_foo)]` - completely custom directives can be emitted by build
41  scripts such as `crates/wasmtime/build.rs`.
42
43[cargo]: https://doc.rust-lang.org/cargo/reference/features.html
44
45To explore built-in `#[cfg]` directives you can use `rustc --print cfg` for your
46host target. This also supports `rustc --print cfg --target ...`.
47
48Finally, `#[cfg]` directive support internal "functions" such as `all(...)`,
49`any(...)`, and `not(..)`.
50
51Attributes in Rust can be applied to syntactic items in Rust, not fragments of
52lexical tokens like C/C++. This means that conditional compilation happens at
53the AST level rather than the lexical level. For example:
54
55```rust,ignore
56#[cfg(foo)]
57fn the_function() { /* ... */ }
58
59#[cfg(not(foo))]
60fn the_function() { /* ... */ }
61```
62
63This can additionally be applied to entire expressions in Rust too:
64
65```rust,ignore
66fn the_function() {
67    #[cfg(foo)]
68    {
69        // ...
70    }
71    #[cfg(not(foo))]
72    {
73        // ...
74    }
75}
76```
77
78The Rust compiler doesn't type-check or analyze anything in
79conditionally-omitted code. It is only required to be syntactically valid.
80
81## Hazards with `#[cfg]`
82
83Conditional compilation in any language can get hairy quickly and Rust is no
84exception. The venerable "`#ifdef` soup" one might have seen in C/C++ is very
85much possible to have in Rust too in the sense that it won't look the same but
86it'll still taste just as bad. In that sense it's worth going over some of the
87downsides of `#[cfg]` in Rust and some hazards to watch out for.
88
89**Unused Imports**
90
91Conditional compilation can be great for quickly excluding an entire function in
92one line, but this might have ramifications if that function was the only use of
93an imported type for example:
94
95```rust,ignore
96use std::ptr::NonNull; //~ WARNING: unused import when `foo` is turned off
97
98#[cfg(foo)]
99fn my_function() -> NonNull<u8> {
100    // ...
101}
102```
103
104**Repetitive Attributes**
105
106Enabling a Cargo feature can add features to existing types which means it can
107lead to repetitive `#[cfg]` annotations such as:
108
109```rust,ignore
110#[cfg(feature = "async")]
111use std::future::Future;
112
113impl<T> Store<T> {
114    #[cfg(feature = "async")]
115    async fn some_new_async_api(&mut self) {
116        // ...
117    }
118
119    #[cfg(feature = "async")]
120    async fn some_other_new_async_api(&mut self) {
121        // ...
122    }
123}
124
125#[cfg(feature = "async")]
126struct SomeAsyncHelperType {
127    // ...
128}
129
130#[cfg(feature = "async")]
131impl SomeAsyncHelperType {
132    // ...
133}
134```
135
136**Boilerplate throughout an implementation**
137
138In addition to being repetitive when defining conditionally compiled code
139there's also a risk of being quite repetitive when using conditionally compiled
140code as well. In its most basic form any usage of a conditionally compiled piece
141of code must additionally be gated as well.
142
143```rust,ignore
144#[cfg(feature = "gc")]
145fn gc() {
146    // ...
147}
148
149fn call_wasm() {
150    #[cfg(feature = "gc")]
151    gc();
152
153    // do the call ...
154
155    #[cfg(feature = "gc")]
156    gc();
157}
158```
159
160**Interactions with ecosystem tooling**
161
162Conditionally compiled code does not always interact well with ecosystem tooling
163in Rust. An example of this is the `cfg_if!` macro where `rustfmt` is unable to
164format the contents of the macro. If there are conditionally defined modules in
165the macro then it means `rustfmt` won't format any modules internally in the
166macro either. Not a great experience!
167
168Here neither `gc.rs` nor `gc_disabled.rs` will be formatted by `cargo fmt`.
169
170```rust,ignore
171cfg_if::cfg_if! {
172    if #[cfg(feature = "gc")] {
173        mod gc;
174        use gc::*;
175    } else {
176        mod gc_disabled;
177        use gc_disabled::*;
178    }
179}
180```
181
182**Combinatorial explosion in testing complexity**
183
184Each crate feature can be turned on and off. Wasmtime supports a range of
185platforms and architectures. It's practically infeasible to test every single
186possible combination of these. This means that inevitably there are going to be
187untested configurations as well as bugs within these configurations.
188
189## Conditional Compilation Style Guide
190
191With some of the basics out of the way, this is intended to document the rough
192current state of Wasmtime and some various principles for writing conditionally
193compiled code. Much of these are meant to address some of the hazards above.
194These guidelines are not always religiously followed throughout Wasmtime's
195repository but PRs to improve things are always welcome!
196
197The main takeaway is that the main goal is **to minimize the number of `#[cfg]`
198attributes necessary in the repository**. Conditional compilation is required no
199matter what so this number can never be zero, but that doesn't mean every other
200line should have `#[cfg]` on it. Otherwise these guidelines need to be applied
201with some understanding that each one is fallible. There's no always-right
202answer unfortunately and style will still differ from person to person.
203
2041. **Separate files** - try to put conditionally compiled code into separate
205   files. By placing `#[cfg]` at the module level you can drastically cut down
206   on annotations by removing the entire module at once. An example of this is
207   that Wasmtime's internal `runtime` module is [feature gated][file-gate] at
208   the top-level.
209
2102. **Only `#[cfg]` definitions, not uses** - try to only use `#[cfg]` when a
211   type or function is defined, not when it's used. Functions and types can be
212   used all over the place and putting a `#[cfg]` everywhere can be quite
213   annoying an brittle to maintain.
214
215   * This isn't a problem if a use-site is already contained in a
216     `#[cfg]` item, such as a module. This can be assisted by lifting `#[cfg]`
217     up "as high as possible". An example of this is Wasmtime's `component`
218     module which uses `#[cfg(feature = "component-model")]` at the root of all
219     component-related functionality. That means that conditionally included
220     dependencies used within `component::*` don't need extra `#[cfg]` annotations.
221
222   * Another common pattern for this is to conditionally define a "dummy" shim
223     interface. The real implementation would live in `foo.rs` while the dummy
224     implementation would live in `foo_disabled.rs`. That means that "foo" is
225     always available but the dummy implementation doesn't do anything. This
226     makes heavy use of zero-sized-types (e.g. `struct Foo;`) and uninhabited
227     types (e.g. `enum Foo {}`) to ensure there is no runtime overhead. An
228     example of this is [`shared_memory.rs`][dummy-enabled] and
229     [`shared_memory_disabled.rs`][dummy-disabled] where the disabled version
230     returns an error on construction and otherwise has trivial implementations
231     of each method.
232
2333. **Off-by-default code should be trivial** - described above it's not possible
234   to test Wasmtime in every possible configuration of `#[cfg]`, so to help
235   reduce the risk of lurking bugs try to ensure that all off-by-default code is
236   trivially correct-by-construction. For "dummy" shims described above this
237   means that methods do nothing or return an error. If off-by-default code is
238   nontrivial then it should have a dedicated CI job to ensure that all
239   conditionally compiled parts are tested one way or another.
240
2414. **Absolute paths are useful, but noisy** - described above it's easy to get
242   into a situation where a conditionally compiled piece of code is the only
243   users of a `use` statement. One easy fix is to remove the `use` and use the
244   fully qualified path (e.g. `param: core::ptr::NonNull`) in the function
245   instead. This reduces the `#[cfg]` to one, just the function in question, as
246   opposed to one on the function and one on the `use`. Beware though that this
247   can make function signatures very long very quickly, so if that ends up
248   happening one of the above points may help instead.
249
2505. **Use `#[cfg]` for anything that requires a new runtime dependency** - one of
251   the primary use cases for `#[cfg]` in Wasmtime is to conditionally remove
252   dependencies at runtime on pieces of functionality. For example if the
253   `async` feature is disabled then stack switching is not necessary to
254   implement. This is a lynchpin of Wasmtime's portability story where we don't
255   guarantee all features compile on all platforms, but the "major" features
256   should compile on all platforms. An example of this is that `threads`
257   requires the standard library, but `runtime` does not.
258
2596. **Don't use `#[cfg]` for compiler features** - in contrast to the previous
260   point it's generally not necessary to plumb `#[cfg]` features to Wasmtime's
261   integration with Cranelift. The runtime size or runtime features required to
262   compile WebAssembly code is generally much larger than just running code
263   itself. This means that conditionally compiled compiler features can just add
264   lots of boilerplate to manage internally without much benefit. Ideally
265   `#[cfg]` is only use for WebAssembly runtime features, not compilation of
266   WebAssembly features.
267
268Note that it's intentional that these guidelines are not 100% comprehensive.
269Additionally they're not hard-and-fast rules in the sense that they're checked
270in CI somewhere. Instead try to follow them if you can, but if you have any
271questions or feel that `#[cfg]` is overwhelming feel free to reach out on Zulip
272or on GitHub.
273
274[file-gate]: https://github.com/bytecodealliance/wasmtime/blob/24620d9ff4cfd3a2a5f681181119eb8b0edaeab5/crates/wasmtime/src/lib.rs#L380-L383
275[high-gate]: https://github.com/bytecodealliance/wasmtime/blob/24620d9ff4cfd3a2a5f681181119eb8b0edaeab5/crates/wasmtime/src/runtime.rs#L55-L56
276[dummy-enabled]: https://github.com/bytecodealliance/wasmtime/blob/main/crates/wasmtime/src/runtime/vm/memory/shared_memory.rs
277[dummy-disabled]: https://github.com/bytecodealliance/wasmtime/blob/24620d9ff4cfd3a2a5f681181119eb8b0edaeab5/crates/wasmtime/src/runtime/vm/memory/shared_memory_disabled.rs
278