Rollup merge of #129630 - alexcrichton:document-broken-c-abi-on-wasm32-u-u, r=workingjubilee
Document the broken C ABI of `wasm32-unknown-unknown` Inspired by discussion on https://github.com/rust-lang/rust/issues/129486 this is intended to at least document the current state of the world in a more public location than throughout a series of issues.
This commit is contained in:
commit
1453cce5e9
@ -195,3 +195,120 @@ conditionally compile code instead. This is notably different to the way native
|
||||
platforms such as x86\_64 work, and this is due to the fact that WebAssembly
|
||||
binaries must only contain code the engine understands. Native binaries work so
|
||||
long as the CPU doesn't execute unknown code dynamically at runtime.
|
||||
|
||||
## Broken `extern "C"` ABI
|
||||
|
||||
This target has what is considered a broken `extern "C"` ABI implementation at
|
||||
this time. Notably the same signature in Rust and C will compile to different
|
||||
WebAssembly functions and be incompatible. This is considered a bug and it will
|
||||
be fixed in a future version of Rust.
|
||||
|
||||
For example this Rust code:
|
||||
|
||||
```rust,ignore (does-not-link)
|
||||
#[repr(C)]
|
||||
struct MyPair {
|
||||
a: u32,
|
||||
b: u32,
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn take_my_pair(pair: MyPair) -> u32;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn call_c() -> u32 {
|
||||
take_my_pair(MyPair { a: 1, b: 2 })
|
||||
}
|
||||
```
|
||||
|
||||
compiles to a WebAssembly module that looks like:
|
||||
|
||||
```wasm
|
||||
(module
|
||||
(import "env" "take_my_pair" (func $take_my_pair (param i32 i32) (result i32)))
|
||||
(func $call_c
|
||||
i32.const 1
|
||||
i32.const 2
|
||||
call $take_my_pair
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
The function when defined in C, however, looks like
|
||||
|
||||
```c
|
||||
struct my_pair {
|
||||
unsigned a;
|
||||
unsigned b;
|
||||
};
|
||||
|
||||
unsigned take_my_pair(struct my_pair pair) {
|
||||
return pair.a + pair.b;
|
||||
}
|
||||
```
|
||||
|
||||
```wasm
|
||||
(module
|
||||
(import "env" "__linear_memory" (memory 0))
|
||||
(func $take_my_pair (param i32) (result i32)
|
||||
local.get 0
|
||||
i32.load offset=4
|
||||
local.get 0
|
||||
i32.load
|
||||
i32.add
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
Notice how Rust thinks `take_my_pair` takes two `i32` parameters but C thinks it
|
||||
only takes one.
|
||||
|
||||
The correct definition of the `extern "C"` ABI for WebAssembly is located in the
|
||||
[WebAssembly/tool-conventions](https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md)
|
||||
repository. The `wasm32-unknown-unknown` target (and only this target, not other
|
||||
WebAssembly targets Rust support) does not correctly follow this document.
|
||||
|
||||
Example issues in the Rust repository about this bug are:
|
||||
|
||||
* [#115666](https://github.com/rust-lang/rust/issues/115666)
|
||||
* [#129486](https://github.com/rust-lang/rust/issues/129486)
|
||||
|
||||
This current state of the `wasm32-unknown-unknown` backend is due to an
|
||||
unfortunate accident which got relied on. The `wasm-bindgen` project prior to
|
||||
0.2.89 was incompatible with the "correct" definition of `extern "C"` and it was
|
||||
seen as not worth the tradeoff of breaking `wasm-bindgen` historically to fix
|
||||
this issue in the compiler.
|
||||
|
||||
Thanks to the heroic efforts of many involved in this, however, `wasm-bindgen`
|
||||
0.2.89 and later are compatible with the correct definition of `extern "C"` and
|
||||
the nightly compiler currently supports a `-Zwasm-c-abi` implemented in
|
||||
[#117919](https://github.com/rust-lang/rust/pull/117919). This nightly-only flag
|
||||
can be used to indicate whether the spec-defined version of `extern "C"` should
|
||||
be used instead of the "legacy" version of
|
||||
whatever-the-Rust-target-originally-implemented. For example using the above
|
||||
code you can see (lightly edited for clarity):
|
||||
|
||||
```shell
|
||||
$ rustc +nightly -Zwasm-c-abi=spec foo.rs --target wasm32-unknown-unknown --crate-type lib --emit obj -O
|
||||
$ wasm-tools print foo.o
|
||||
(module
|
||||
(import "env" "take_my_pair" (func $take_my_pair (param i32) (result i32)))
|
||||
(func $call_c (result i32)
|
||||
;; ...
|
||||
)
|
||||
;; ...
|
||||
)
|
||||
```
|
||||
|
||||
which shows that the C and Rust definitions of the same function now agree like
|
||||
they should.
|
||||
|
||||
The `-Zwasm-c-abi` compiler flag is tracked in
|
||||
[#122532](https://github.com/rust-lang/rust/issues/122532) and a lint was
|
||||
implemented in [#117918](https://github.com/rust-lang/rust/issues/117918) to
|
||||
help warn users about the transition if they're using `wasm-bindgen` 0.2.88 or
|
||||
prior. The current plan is to, in the future, switch `-Zwasm-c-api=spec` to
|
||||
being the default. Some time after that the `-Zwasm-c-abi` flag and the
|
||||
"legacy" implementation will all be removed. During this process users on a
|
||||
sufficiently updated version of `wasm-bindgen` should not experience breakage.
|
||||
|
Loading…
Reference in New Issue
Block a user