Open Manishearth opened 2 months ago
cc @RalfJung @bjorn3 @alexcrichton
Isn't this exactly what https://github.com/rust-lang/rust/pull/117919 was about
Ah, thanks! Yes, that fixes the "Rust doesn't match tool conventions" problem.
I'd still love to see documentation of the legacy ABI.
In particular two things stand out as super strange and requiring documentation:
(I am not yet sure if that is indeed the actual behavior, fwiw, this is just what it appears to be to me from poking at it. I have not attempted to disentangle the code.)
To confirm, this issue is about the wasm32-unknown-unknown
target, right? If so then the goal is to one day stabilize -Zwasm-c-abi
as "this is just how the target works now". The differences here you're seeing are a result of:
wasm32-unknown-unknown
was added the tool-conventions repo didn't exist yet.wasm-bindgen
it relied on the fact that struct Foo { a: usize, b: usize }
was passed as two scalars.In the end wasm-bindgen
got big enough that there was motivation to not break it. For a long time there wasn't a lot of spare effort/motivation to fix the compiler. Since then through heroic efforts of those involved wasm-bindgen is now "fixed" and ready to work with -Zwasm-c-abi
. All that's left now is the final push over the hump to get it stable.
Put another way I would personally consider (a) breaking changes are on the table here and should be pushed through and (b) I wouldn't bother documenting the "legacy ABI" because the documentation is "whatever the consequence of whatever alex wrote years ago" which is not really satisfying or understandable documentation.
In particular two things stand out as super strange and requiring documentation:
This is just a consequence of https://github.com/rust-lang/rust/issues/115666, and therefore unstable codegen backend implementation details leaking through. (The ABI adjustment code is terrible, I can't blame anyone for getting it wrong. It really needs a complete refactor... I think I improved things slightly by making the defaults less bad, but a lot of work is left to be done; Cc https://github.com/rust-lang/rust/issues/119183) We do not intend to make any stability guarantees for those codegen backend implementation details.
IMO this is a duplicate of https://github.com/rust-lang/rust/issues/122532 and/or https://github.com/rust-lang/rust/issues/115666. Is anyone working towards getting that ready to ship?
EDIT:
Since then through heroic efforts of those involved wasm-bindgen is now "fixed" and ready to work with -Zwasm-c-abi.
Looks like yes, people are working on that. :heart:
The biggest thing that would help is if more uses of wasm-bindgen in the wild were 0.2.89 or higher (0.2.88, the version with the fix, got yanked, but the later versions still carry the fix). Based on the crates.io downloads, it only has about ~75% penetration: https://crates.io/crates/wasm-bindgen
Maybe that's enough?
I wouldn't bother documenting the "legacy ABI" because the documentation is "whatever the consequence of whatever alex wrote years ago" which is not really satisfying or understandable documentation.
Given that allocating across FFI in JS is actually kinda costly, I actually do think that the "legacy" ABI has one bit of design I'd love to still have access to: passing around aggregates as "piles of scalars". The problem is that it currently does this inconsistently in a way that makes padding matter, and solving that problem brings up questions for what unions are expected to do. But this is solvable.
That said, I don't know if there's much appetite for polishing up the legacy backend.
IMO this is a duplicate of https://github.com/rust-lang/rust/issues/122532 and/or https://github.com/rust-lang/rust/issues/115666. Is anyone working towards getting that ready to ship?
I think the current behavior should still be documented somewhere.
If you're curious there's a long discussion at https://github.com/WebAssembly/tool-conventions/issues/88 about changing the C ABI itself. It's unfortunately not going to be easy.
Otherwise I've opened https://github.com/rust-lang/rust/pull/129630 to document the current state of the world (which hopefully that documentation won't live for long)
Thank you!
I've also written some docs for the legacy ABI based on my investigations in https://github.com/rust-diplomat/diplomat/pull/663
This is a bit of a hybrid bug report and documentation request. I suspect the "bug" will require a breaking change to fix so may be a non-starter.
In Diplomat we've been writing bindings to Rust from JS/Wasm. One thing we support is passing structs over FFI, by-value.
According to the tool conventions, multi-scalar-field structs are passed "indirectly", and single-scalar structs and scalars are passed directly by-value.
This is not what Rust does. This has previously come up in https://github.com/rust-lang/rust/issues/81386. What Rust does is that it always passes structs by-value, which on the JS side means that the struct is "splatted" across the arguments including padding.
For example, the following type and function
gets passed over LLVM IR as
In JS,
big
needs to be called aswasm.big(a, 0, b, 0, 0, c)
, with0
s for the padding fields. Note that the padding fields can be different sizes, which is usually irrelevant but important here since "two i16s" and "one i32" end up meaning a different number of parameters. As far as I can tell the padding field has the same size as the alignment of the previous "real" field, but I can't find any documentation on this or even know whether this is a choice on LLVM or Rust's side.It gets worse, however. Rust seems to special case scalar pairs:
Here, despite
Small
having padding,small()
gets called aswasm.small(a, b)
, because the fields got splatted out in the LLVM IR itself, without padding.This is even stranger when comparing with the tool conventions because they have no mention of scalar pairs.
It would be really nice if Rust followed the documented tool conventions. I suspect that's not going to happen, and, besides, direct parameter passing is likely more efficient here[^1].
Failing that, it seems like it would be nice if "pairs" did not have special behavior compared to structs with more than 2 scalars in them. Ideally I'd like the scalar pair behavior to apply to all structs: always splat out structs into fields, never require padding be passed over FFI. I'm not sure if such a breaking change is possible, though.
Failing that, I think this behavior should be carefully documented. I've been discovering this by trial-and-error, and Rust's behavior contradicts the extant documentation, which makes it even more crucial to have documentation on how Rust diverges.
As far as I can tell, this is not a problem for wasm-bindgen since wasm-bindgen doesn't pass structs over FFI by-value, though the failing test mentioned in https://github.com/rust-lang/rust/issues/81386 seems to indicate they care some amount.
[^1]: Indirect passing means that the JS side basically has to allocate a heap object each time it wishes to call such a function.