Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clang and Rustc use incompatible Wasm signatures for struct arguments passed by value #130

Closed
maxbrunsfeld opened this issue Oct 1, 2019 · 12 comments

Comments

@maxbrunsfeld
Copy link

maxbrunsfeld commented Oct 1, 2019

Apologies in advance if this is the wrong place to open this issue.

Summary

When compiling with the wasm32-unknown-unknown target triple, Clang 8.0 and Rustc 1.38.0 use two different Wasm function signatures for an extern "C" function that accepts a struct by value. This causes a linker error when trying to link C and Rust code together.

Details

There a minimal project which reproduces the problem here.

The C code contains this:

typedef struct {
  uint32_t line;
  uint32_t column;
} Point;

Point add_points_in_c(Point a, Point b) {
  // ...
}

The Rust code contains this:

#[repr(C)]
pub struct Point {
    pub line: u32,
    pub column: u32,
}

extern "C" {
    fn add_points_in_c(a: Point, b: Point) -> Point;
}

When trying to link the two, I get this error:

  = note: rust-lld: error: function signature mismatch: add_points_in_c
          >>> defined as (i32, i32, i32, i32, i32) -> void in wasm_lib_with_c.nxdi25wtl7eb7vo.rcgu.o
          >>> defined as (i32, i32, i32) -> void in libwasm-c-component.a(lib.o)

It looks like Clang always passes the structs via pointer, whereas Rust unpacks their fields into separate i32 parameters.

Both strategies seem reasonable, but it would be great if the two compilers would agree on a calling convention for this, so that C and Rust code could be linked together when compiling for Wasm, like they can when compiling for other targets.

Current Workaround

If I update the add_points_in_c function accept the parameters via a pointer, the project builds and runs as expected. I've done this here.

@tlively
Copy link
Member

tlively commented Oct 1, 2019

Yep, this is one of the issues being addressed in rust-lang/rust#63649.

@maxbrunsfeld
Copy link
Author

⚡️ Thanks for the reply @tlively. Looking forward to trying again when your PR lands.

@maxbrunsfeld
Copy link
Author

maxbrunsfeld commented Oct 2, 2019

@tlively There's one other error I'm seeing with Rust -> C FFI on wasm32-unknown-unknown, and I'm curious if you think it will be affected by your PR.

I am seeing an Invalid index into function table error thrown from my WebAssembly module at a line where a C function performs a call_indirect to a function pointer that is implemented in Rust. The function being called doesn't take any structs by value.

I haven't yet been able to come up with a minimal reproduction case for this one. So far, my minimal examples of C code indirectly calling Rust functions have all worked ok, so it seems to be something more subtle about the functions in my codebase.

@tlively
Copy link
Member

tlively commented Oct 2, 2019

@maxbrunsfeld That does sound trickier. I'm not exactly sure what could be going on there without looking at it. How are you linking your C and Rust code together? I'd be happy to take a look if you can share your project.

@maxbrunsfeld
Copy link
Author

Unfortunately, I can't share the project right now, but thanks a lot for being willing to check it out. I'll probably just try again after your PR lands, and if the error is still happening, I'll work a little harder to isolate the problem and come up with a minimal repro.

@tlively
Copy link
Member

tlively commented Oct 2, 2019

Sounds good. I'm still curious about how you're doing the linking, though. Also, have you tried using the wasm32-unknown-emscripten target instead of wasm32-unknown-unknown? That target explicitly tries to support Rust/C interoperability, unlike wasm32-unknown-unknown.

@maxbrunsfeld
Copy link
Author

maxbrunsfeld commented Oct 2, 2019

I have tried the wasm32-unknown-emscripten target, but I'd really like to use wasm-bindgen if possible, and AFAICT, that library doesn't work with the Emscripten target. My C code has minimal dependencies, so I don't need any of the I/O shims that Emscripten provides, and I only need a few functions from libc. For those libc functions, I'm statically compiling parts of musl into the library via build.rs.

As for the linking, rust-lld seems to be capable of linking together the wasm object files created by Clang with those created by rustc, so I can just do cargo build like usual.

I'm not yet sure that wasm-bindgen and wasm32-unknown-unknown are going to work for my actual project, but it does work for my minimal example repo here.

@sunfishcode
Copy link
Member

Also, have you tried using the wasm32-unknown-emscripten target instead of wasm32-unknown-unknown? That target explicitly tries to support Rust/C interoperability, unlike wasm32-unknown-unknown.

This sounds surprising; what Rust/C interoperability do we do for wasm32-unknown-emscripten that we don't do for wasm32-unknown-unknown?

@tlively
Copy link
Member

tlively commented Oct 2, 2019

I'm not sure it's a matter of technical difference but rather a matter of intent. One of the primary reasons to use the emscripten targets is to be able to use ports such as SDL2 and OpenGL and features such as Asyncify that help paper over the gaps between the web and traditional C environments. In contrast, that has never seemed to be a primary goal or advertised use case for the wasm32-unknown-unknown target.

@alexcrichton
Copy link
Collaborator

FWIW there's no inherent incompatibility with using the Rust wasm32-unknown-unknown target and combining that with C code. As you've found out @maxbrunsfeld so long as you've got the right Clang compiler you should be good to go!

We've typically said, though, that if you're using C code you probably want to use the emscripten target. This is not due to incompatibilities but largely due to convenience. As you've also found out @maxbrunsfeld you've had to bring your own C library with symbols and all, which Rust doesn't otherwise help you do for wasm32-unknown-unknown.

@tlively
Copy link
Member

tlively commented Oct 17, 2019

rust-lang/rust#65251 is merged, so this FFI mismatch is fixed for Rust's emscripten targets. It's not quite fixed for wasm32-unknown-unknown yet because wasm-bindgen needs fixing, but at least is on the Rust team's radar now, so I'll close this issue. Feel free to open a new issue tracking the remaining work on the Rust repo.

@rafaelcaricio
Copy link

This error does also impact the target wasm32-wasi. I am experiencing exactly the same issue described in this ticket.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants