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