Combine rust and c for targeting wasm

Hey everybody,

I would like to ask a question regarding combining c code with rust and compile it as wasm target.

What I would like to do is to have some rust code - compile it to a wasm module and than take my c code project and compile it to wasm as well with the other module.

The headers of my rust library I would generate using cbindgen in order to reference it in the c code. To be more specific I would like to use rust for business logic layer and using Raylib for rendering targeting wasm. Raylib is a graphic abstraction in plain c. As I cant use ffi in wasm I just dont know how to make it.

Any help would be great.

regards
Schr3da

(cc @Michael-F-Bryan, IIRC, they have worked with more complex linkage setups related to Rust and Wasm).

The main objective for you will be to be able to compile that C code to Wasm already, independently of Rust. It's a necessary step for it to later work with your Rust code, and I suspect it will be sufficient, since you can compile the C stuff and the Rust stuff as "Wasm object files", and then link them all together as a wasm library afterwards.

In order to compile C code as a Wasm object or library files, you may want to look for the emscripten keyword.

In other words:

that statement is incorrect: Wasm can do FFI just fine, with a compilation model very similar to that of native C binaries and libraries, the only thing is that it's backed by a special / different machine code / bytecode model, that of Wasm.

So what you'll have to do is cross-compile from your native / host architecture, to that of Wasm (see rustup target list | grep wasm for a list of candidates), kind of similar to the exercise of compiling your Rust and C code for a Raspberry Pi but from your desktop computer: they can do FFI, but using a different architecture.

1 Like

Funny story, we've actually wanted to do something similar at work :sweat_smile:

If by FFI you mean declaring an extern "C" function that is later provided by during linking then you can definitely do FFI when compiling to WebAssembly.

To work around a particular limitation in the glue generated by wit-bindgen[1], we copied the way Rust's #[global_allocator] works. In a shared crate we would declare some sort of fn __proc_block_metadata() -> Metadata function and downstream crates would use a macro to generate a function with the correct name and signature.

Then, the compiler will compile each crate into an object file containing WebAssembly bytecode and at the very end the linker will take all these WebAssembly object files and link them together into a single WebAssembly binary, resolving our __proc_block_metadata symbols in the process.

So in that sense, as long as you can get object files containing WebAssembly, it should theoretically be possible to manually link them together and resolve your FFI symbols.

I tried to do something similar about a year ago but in the opposite direction. I had a Rust crate and was using the cc crate at build time to try to compile some C code to WebAssembly and link to it. Just like you would do when implementing a normal *-sys crate.

I think I ran into some issues with linking to the object files or maybe it was because Emscripten uses its own ABI when compiling to WebAssembly. Either way I had other things on my plate at the time, so after half an hour of messing around I decided to throw it in the too hard basket and try something else.

If you squint hard, this is very similar to the solution from above, except the upstream dependency is an external C library instead of a Rust crate.


Until now, I've just talked about trying to compile multiple libraries into a single WebAssembly binary, but there is another solution.

When you load a WebAssembly binary (e.g. using WebAssembly.instantiate() in the browser or wasmer in native code), the binary can request certain functions be provided by the host.

However, there is nothing stopping the host from using the functions exported by one WebAssembly module as the imports of another. This lets you manually do dynamic linking and could be a solution for you.

The idea is you would compile your Rust code to a *.wasm file and your C code goes in another *.wasm file. At runtime, you would first instantiate the Rust binary then use its exported functions to satisfy the C binary's requirements.

We were originally hoping Wasmer could create a similar sort of static linking solution which takes two WebAssembly binaries and "merges" them, so I put together an experiment which shows how you do this dynamic linking manually, as well as writing up our goal in the README. The actual "linking" step happens on this line.

You can do something very similar in the browser and the flow is almost identical plus/minus some async, but I don't have any handy links to source code for that :disappointed:

Our final solution looks quite similar to this, except instead of writing the extern "C" functions manually and having to try and smuggle high-level objects across an interface that only allows integers and floats, we use the wit-bindgen project to declare high-level interfaces and generate glue code for the guest and host. The wit-bindgen code generator also generates glue for a C guest, so that's a viable option for you too.


I don't know if that big wall of text answers your original question, but something like what you want is definitely possible to do. Hopefully some of those links and experiences can help you along the way.


  1. We wanted to generate the glue code for exposing functions between guest and host in a shared crate, but that meant we needed a way for the final crate to inject functionality into one of its dependencies. ↩ī¸Ž

2 Likes

Thanks a lot for the input @Yandros.
That was what I tried before. I compiled my C code using emsdk and my rust code using wasm32-unknown-unknown. But didnt manage to merge the 2 wasm files together so that I could stay totally in the wasm world and not need to rely on javascript additions.

Thanks @Michael-F-Bryan I had something similar idea in my head but you made more sophisticated by passing and wasm object (with a declared interface which is known by both ends). I was hoping that I dont need it but I guess that is the way to go.

Third solution would be sending data between the different modules based on string messages but this is obviously not nice and slower :smiley:

I will close the question as I think you helped a lot :smiley:

regards
Schr3da

You don't technically need something like wit-bindgen, it just makes things easier because you can declare the high-level interface and it'll do the boring work of wiring it up. If you are familiar with gRPC or OpenAPI, it's the same idea.

Hi, I am part of "everybody" and thank you for including me. My ignorance may be of aid. I can not understand much of this thread and I see it is solved, but still will offer my "possible" mammal brute force solution.....

If you turn the wasm files in wat (wasm text) you could maybe patch those two text files together into one wat file and turn that one wat file back into wasm.

I have not done this myself, but looking at some wat files, I envision it could be done without too much "work?"

1 Like

If you are compiling your C code with emscripten, you should probably use the emscripten target (wasm32-unknown-emscripten) for rust too. Note that the wasm32-unknown-unknown target has extern "C" be an abi incompatible with C for historic reasons. We unfortunately can't change it without breaking wasm-bindgen.