Skip to content

Commit 0e2fed9

Browse files
authored
Merge pull request #1531 from alexcrichton/wasm-c-abi-changes
C ABI Changes for `wasm32-unknown-unknown`
1 parent 13cefbf commit 0e2fed9

File tree

1 file changed

+273
-0
lines changed

1 file changed

+273
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
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

Comments
 (0)