Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 0e2fed9

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

File tree

Expand file treeCollapse file tree

1 file changed

+273
-0
lines changed
Filter options
Expand file treeCollapse file tree

1 file changed

+273
-0
lines changed
+273Lines changed: 273 additions & 0 deletions
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.