|  | 
|  | 1 | ++++ | 
|  | 2 | +layout = "post" | 
|  | 3 | +date = 2025-04-04 | 
|  | 4 | +title = "C ABI Changes for `wasm32-unknown-unknown`" | 
|  | 5 | +author = "Alex Crichton" | 
|  | 6 | ++++ | 
|  | 7 | + | 
|  | 8 | +The `extern "C"` ABI for the `wasm32-unknown-unknown` target has been using a | 
|  | 9 | +non-standard definition since the inception of the target in that it does not | 
|  | 10 | +implement the [official C ABI of WebAssembly][tool-conventions] and it | 
|  | 11 | +additionally [leaks internal compiler implementation details][leak-details] of | 
|  | 12 | +both the Rust compiler and LLVM. This will change in a future version of the | 
|  | 13 | +Rust compiler and the [official C ABI][tool-conventions] will be used instead. | 
|  | 14 | + | 
|  | 15 | +This post details some history behind this change and the rationale for why it's | 
|  | 16 | +being announced here, but you can skip straight to ["Am I | 
|  | 17 | +affected?"](#am-i-affected) as well. | 
|  | 18 | + | 
|  | 19 | +[tool-conventions]: https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md | 
|  | 20 | +[leak-details]: https://github.com/rust-lang/rust/issues/115666 | 
|  | 21 | + | 
|  | 22 | +## History of `wasm32-unknown-unknown`'s C ABI | 
|  | 23 | + | 
|  | 24 | +When the `wasm32-unknown-unknown` target [was originally added][inception] in | 
|  | 25 | +2017, not much care was given to the exact definition of the `extern "C"` ABI at | 
|  | 26 | +the time. In 2018 [an ABI definition was added just for wasm][orig-abi] and the | 
|  | 27 | +target is still using this definition [to this day][current-abi]. This | 
|  | 28 | +definitions has become more and more problematic over time and while some issues | 
|  | 29 | +have been fixed, the root cause still remains. | 
|  | 30 | + | 
|  | 31 | +Notably this ABI definition does not match the [tool-conventions] definition of | 
|  | 32 | +the C API, which is the current standard for how WebAssembly toolchains should | 
|  | 33 | +talk to one another. Originally this non-standard definition was used for all | 
|  | 34 | +WebAssembly based targets except Emscripten, but [this changed in 2021][fix-wasi] | 
|  | 35 | +where the WASI targets for Rust use a corrected ABI definition. Still, however, | 
|  | 36 | +the non-standard definition remained in use for `wasm32-unknown-unknown`. | 
|  | 37 | + | 
|  | 38 | +The time has now come to correct this historical mistake and the Rust compiler | 
|  | 39 | +will soon be using a correct ABI definition for the `wasm32-unknown-unknown` | 
|  | 40 | +target. This means, however, that generated WebAssembly binaries will be | 
|  | 41 | +different than before. | 
|  | 42 | + | 
|  | 43 | +## What is a WebAssembly C ABI? | 
|  | 44 | + | 
|  | 45 | +The definition of an ABI answers questions along the lines of: | 
|  | 46 | + | 
|  | 47 | +* What registers are arguments passed in? | 
|  | 48 | +* What registers are results passed in? | 
|  | 49 | +* How is a 128-bit integers passed as an argument? | 
|  | 50 | +* How is a `union` passed as a return value? | 
|  | 51 | +* When are parameters passed through memory instead of registers? | 
|  | 52 | +* What is the size and alignment of a type in memory? | 
|  | 53 | + | 
|  | 54 | +For WebAssembly these answers are a little different than native platforms. | 
|  | 55 | +For example, WebAssembly does not have physical registers and functions must all | 
|  | 56 | +be annotated with a type. What WebAssembly does have is types such as `i32`, | 
|  | 57 | +`i64`, `f32`, and `f64`. This means that for WebAssembly an ABI needs to define | 
|  | 58 | +how to represent values in these types. | 
|  | 59 | + | 
|  | 60 | +This is where the [tool-conventions] document comes in. That document provides a | 
|  | 61 | +definition for how to represent primitives in C in the WebAssembly format, and | 
|  | 62 | +additionally how function signatures in C are mapped to function signatures in | 
|  | 63 | +WebAssembly. For example a Rust `u32` is represented by a WebAssembly `i32` and | 
|  | 64 | +is passed directly as a parameter as a function argument. If the Rust structure | 
|  | 65 | +`#[repr(C)] struct Pair(f32, f64)` is returned from a function then a return | 
|  | 66 | +pointer is used which must have alignment 8 and size of 16 bytes. | 
|  | 67 | + | 
|  | 68 | +In essence, the WebAssembly C ABI is acting as a bridge between C's type system | 
|  | 69 | +and the WebAssembly type system. This includes details such as in-memory layouts | 
|  | 70 | +and translations of a C function signature to a WebAssembly function signature. | 
|  | 71 | + | 
|  | 72 | +## How is `wasm32-unknown-unknown` non-standard? | 
|  | 73 | + | 
|  | 74 | +Despite the ABI definition today being non-standard, many aspects of it are | 
|  | 75 | +still the same as what [tool-conventions] specifies. For example, size/alignment | 
|  | 76 | +of types is the same as it is in C. The main difference is how function | 
|  | 77 | +signatures are calculated. An example (where you can follow along on [godbolt]) | 
|  | 78 | +is: | 
|  | 79 | + | 
|  | 80 | +```rust | 
|  | 81 | +#[repr(C)] | 
|  | 82 | +pub struct Pair { | 
|  | 83 | +    x: u32, | 
|  | 84 | +    y: u32, | 
|  | 85 | +} | 
|  | 86 | + | 
|  | 87 | +#[unsafe(no_mangle)] | 
|  | 88 | +pub extern "C" fn pair_add(pair: Pair) -> u32 { | 
|  | 89 | +    pair.x + pair.y | 
|  | 90 | +} | 
|  | 91 | +``` | 
|  | 92 | + | 
|  | 93 | +This will generate the following WebAssembly function: | 
|  | 94 | + | 
|  | 95 | +```wasm | 
|  | 96 | +(func $pair_add (param i32 i32) (result i32) | 
|  | 97 | +  local.get 1 | 
|  | 98 | +  local.get 0 | 
|  | 99 | +  i32.add | 
|  | 100 | +) | 
|  | 101 | +``` | 
|  | 102 | + | 
|  | 103 | +Notably you can see here that the struct `Pair` was "splatted" into its two | 
|  | 104 | +components so the actual `$pair_add` function takes two arguments, the `x` and | 
|  | 105 | +`y` fields. The [tool-conventions], however specifically says that "other | 
|  | 106 | +struct[s] or union[s]" are passed indirectly, notably through memory. We can see | 
|  | 107 | +this by compiling this C code: | 
|  | 108 | + | 
|  | 109 | +```c | 
|  | 110 | +struct Pair { | 
|  | 111 | +    unsigned x; | 
|  | 112 | +    unsigned y; | 
|  | 113 | +}; | 
|  | 114 | + | 
|  | 115 | +unsigned pair_add(struct Pair pair) { | 
|  | 116 | +    return pair.x + pair.y; | 
|  | 117 | +} | 
|  | 118 | +``` | 
|  | 119 | +
 | 
|  | 120 | +which yields the generated function: | 
|  | 121 | +
 | 
|  | 122 | +```wasm | 
|  | 123 | +(func (param i32) (result i32) | 
|  | 124 | +  local.get 0 | 
|  | 125 | +  i32.load offset=4 | 
|  | 126 | +  local.get 0 | 
|  | 127 | +  i32.load | 
|  | 128 | +  i32.add | 
|  | 129 | +) | 
|  | 130 | +``` | 
|  | 131 | + | 
|  | 132 | +Here we can see, sure enough, that `pair` is passed in linear memory and this | 
|  | 133 | +function only has a single argument, not two. This argument is a pointer into | 
|  | 134 | +linear memory which stores the `x` and `y` fields. | 
|  | 135 | + | 
|  | 136 | +The Diplomat project has [compiled a much more comprehensive overview][quirks] | 
|  | 137 | +than this and it's recommended to check that out if you're curious for an even | 
|  | 138 | +deeper dive. | 
|  | 139 | + | 
|  | 140 | +## Why hasn't this been fixed long ago already? | 
|  | 141 | + | 
|  | 142 | +For `wasm32-unknown-unknown` it was well-known at the time in 2021 when WASI's | 
|  | 143 | +ABI was updated that the ABI was non-standard. Why then has the ABI not been | 
|  | 144 | +fixed like with WASI? | 
|  | 145 | +The main reason originally for this was the [wasm-bindgen | 
|  | 146 | +project][wasm-bindgen]. | 
|  | 147 | + | 
|  | 148 | +In `wasm-bindgen` the goal is to make it easy to integrate Rust into a web | 
|  | 149 | +browser with WebAssembly. JavaScript is used to interact with host APIs and the | 
|  | 150 | +Rust module itself. Naturally, this communication touches on a lot of ABI | 
|  | 151 | +details! The problem was that `wasm-bindgen` relied on the above example, | 
|  | 152 | +specifically having `Pair` "splatted" across arguments instead of passed | 
|  | 153 | +indirectly. The generated JS wouldn't work correctly if the argument was passed | 
|  | 154 | +in-memory. | 
|  | 155 | + | 
|  | 156 | +At the time this was discovered it was found to be significantly difficult to | 
|  | 157 | +fix `wasm-bindgen` to not rely on this splatting behavior. At the time it also | 
|  | 158 | +wasn't thought to be a widespread issue nor was it costly for the compiler to | 
|  | 159 | +have a non-standard ABI. Over the years though the pressure has mounted. The | 
|  | 160 | +Rust compiler is carrying an [ever-growing list of hacks][leak-details] to work | 
|  | 161 | +around the non-standard C ABI on `wasm32-unknown-unknown`. Additionally more | 
|  | 162 | +projects have started to rely on this "splatting" behavior and the risk has | 
|  | 163 | +gotten greater that there are more unknown projects relying on the non-standard | 
|  | 164 | +behavior. | 
|  | 165 | + | 
|  | 166 | +In late 2023 [the wasm-bindgen project fixed bindings generation][wbgfix] to be | 
|  | 167 | +unaffected by the transition to the standard definition of `extern "C"`. In the following months | 
|  | 168 | +a [future-incompat lint was added to rustc][fcw1] to specifically migrate users | 
|  | 169 | +of old `wasm-bindgen` versions to a "fixed" version. This was in anticipation of | 
|  | 170 | +changing the ABI of `wasm32-unknown-unknown` once enough time had passed. Since | 
|  | 171 | +early 2025 users of old `wasm-bindgen` versions [will now receive a hard | 
|  | 172 | +error][hard-error] asking them to upgrade. | 
|  | 173 | + | 
|  | 174 | +Despite all this heroic effort done by contributors, however, it has now come to | 
|  | 175 | +light that there are more projects than `wasm-bindgen` relying on this | 
|  | 176 | +non-standard ABI definition. Consequently this blog post is intended to serve as | 
|  | 177 | +a notice to other users on `wasm32-unknown-unknown` that the ABI break is | 
|  | 178 | +upcoming and projects may need to be changed. | 
|  | 179 | + | 
|  | 180 | +## Am I affected? | 
|  | 181 | + | 
|  | 182 | +If you don't use the `wasm32-unknown-unknown` target, you are not affected by | 
|  | 183 | +this change. If you don't use `extern "C"` on the `wasm32-unknown-unknown` | 
|  | 184 | +target, you are also not affected. If you fall into this bucket, however, you | 
|  | 185 | +may be affected! | 
|  | 186 | + | 
|  | 187 | +To determine the impact to your project there are a few tools at your disposal: | 
|  | 188 | + | 
|  | 189 | +* A new [future-incompat warning][fcw2] has been added to the Rust compiler | 
|  | 190 | +  which will issue a warning if it detects a signature that will change when the | 
|  | 191 | +  ABI is changed. | 
|  | 192 | +* In 2023 a [`-Zwasm-c-abi=(legacy|spec)` flag was added][specflag] to the Rust | 
|  | 193 | +  compiler. This defaults to `-Zwasm-c-abi=legacy`, the non-standard definition. | 
|  | 194 | +  Code can use `-Zwasm-c-abi=spec` to use the standard definition of the C ABI | 
|  | 195 | +  for a crate to test out if changes work. | 
|  | 196 | + | 
|  | 197 | +The best way to test your crate is to compile with `nightly-2025-03-27` | 
|  | 198 | +or later, ensure there are no warnings, and then test your project still works | 
|  | 199 | +with `-Zwasm-c-abi=spec`. If all that passes then you're good to go and the | 
|  | 200 | +upcoming change to the C ABI will not affect your project. | 
|  | 201 | + | 
|  | 202 | +## I'm affected, now what? | 
|  | 203 | + | 
|  | 204 | +So you're using `wasm32-unknown-unknown`, you're using `extern "C"`, and the | 
|  | 205 | +nightly compiler is giving you warnings. Additionally your project is broken | 
|  | 206 | +when compiled with` -Zwasm-c-abi=spec`. What now? | 
|  | 207 | + | 
|  | 208 | +At this time this will unfortunately be a somewhat rough transition period for | 
|  | 209 | +you. There are a few options at your disposal but they all have their downsides: | 
|  | 210 | + | 
|  | 211 | +1. Pin your Rust compiler version to the current stable, don't update until the | 
|  | 212 | +   ABI has changed. This means that you won't get any compiler warnings (as old | 
|  | 213 | +   compilers don't warn) and additionally you won't get broken when the ABI | 
|  | 214 | +   changes (as you're not changing compilers). Eventually when you update to a | 
|  | 215 | +   stable compiler with `-Zwasm-c-abi=spec` as the default you'll have to port | 
|  | 216 | +   your JS or bindings to work with the new ABI. | 
|  | 217 | + | 
|  | 218 | +2. Update to Rust nightly as your compiler and pass `-Zwasm-c-abi=spec`. This is | 
|  | 219 | +   front-loading the work required in (1) for your target. You can get your | 
|  | 220 | +   project compatible with `-Zwasm-c-abi=spec` today. The downside of this | 
|  | 221 | +   approach is that your project will only work with a nightly compiler and | 
|  | 222 | +   `-Zwasm-c-abi=spec` and you won't be able to use stable until the default is | 
|  | 223 | +   switched. | 
|  | 224 | + | 
|  | 225 | +3. Update your project to not rely on the non-standard behavior of | 
|  | 226 | +   `-Zwasm-c-abi=legacy`. This involves, for example, not passing | 
|  | 227 | +   structs-by-value in parameters. You can pass `&Pair` above, for example, | 
|  | 228 | +   instead of `Pair`. This is similar to (2) above where the work is done | 
|  | 229 | +   immediately to update a project but has the benefit of continuing to work on | 
|  | 230 | +   stable Rust. The downside of this, however, is that you may not be able to | 
|  | 231 | +   easily change or update your C ABI in some situations. | 
|  | 232 | + | 
|  | 233 | +4. Update to Rust nightly as your compiler and pass `-Zwasm-c-abi=legacy`. This | 
|  | 234 | +   will silence compiler warnings for now but be aware that the ABI will still | 
|  | 235 | +   change in the future and the `-Zwasm-c-abi=legacy` option will be removed | 
|  | 236 | +   entirely. When the `-Zwasm-c-abi=legacy` option is removed the only option | 
|  | 237 | +   will be the standard C ABI, what `-Zwasm-c-abi=spec` today enables. | 
|  | 238 | + | 
|  | 239 | +If you have uncertainties, questions, or difficulties, feel free to reach out on | 
|  | 240 | +[the tracking issue for the future-incompat warning][tracking] or on Zulip. | 
|  | 241 | + | 
|  | 242 | +## Timeline of ABI changes | 
|  | 243 | + | 
|  | 244 | +At this time there is not an exact timeline of how the default ABI is going to | 
|  | 245 | +change. It's expected to take on the order of 3-6 months, however, and will look | 
|  | 246 | +something roughly like this: | 
|  | 247 | + | 
|  | 248 | +* 2025 March: (soon) - a [future-incompat warning][fcw2] will be added to the | 
|  | 249 | +  compiler to warn projects if they're affected by this ABI change. | 
|  | 250 | +* 2025-05-15: this future-incompat warning will reach the stable Rust channel as | 
|  | 251 | +  1.87.0. | 
|  | 252 | +* 2025 Summer: (ish) - the `-Zwasm-c-abi` flag will be removed from the compiler | 
|  | 253 | +  and the `legacy` option will be entirely removed. | 
|  | 254 | + | 
|  | 255 | +Exactly when `-Zwasm-c-abi` is removed will depend on feedback from the | 
|  | 256 | +community and whether the future-incompat warning triggers much. It's hoped that | 
|  | 257 | +soon after the Rust 1.87.0 is stable, though, that the old legacy ABI behavior | 
|  | 258 | +can be removed. | 
|  | 259 | + | 
|  | 260 | +[wbgfix]: https://github.com/rustwasm/wasm-bindgen/pull/3595 | 
|  | 261 | +[specflag]: https://github.com/rust-lang/rust/pull/117919 | 
|  | 262 | +[fcw1]: https://github.com/rust-lang/rust/pull/117918 | 
|  | 263 | +[fcw2]: https://github.com/rust-lang/rust/pull/138601 | 
|  | 264 | +[hard-error]: https://github.com/rust-lang/rust/pull/133951 | 
|  | 265 | +[inception]: https://github.com/rust-lang/rust/pull/45905 | 
|  | 266 | +[orig-abi]: https://github.com/rust-lang/rust/pull/48959 | 
|  | 267 | +[current-abi]: https://github.com/rust-lang/rust/blob/78948ac259253ce89effca1e8bb64d16f4684aa4/compiler/rustc_target/src/callconv/wasm.rs#L76-L114 | 
|  | 268 | +[fix-wasi]: https://github.com/rust-lang/rust/pull/79998 | 
|  | 269 | +[godbolt]: https://godbolt.org/z/fExj4M4no | 
|  | 270 | +[conventions-struct]: https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md#function-arguments-and-return-values | 
|  | 271 | +[wasm-bindgen]: https://github.com/rustwasm/wasm-bindgen | 
|  | 272 | +[tracking]: https://github.com/rust-lang/rust/issues/138762 | 
|  | 273 | +[quirks]: https://github.com/rust-diplomat/diplomat/blob/main/docs/wasm_abi_quirks.md | 
0 commit comments