WASM, Rust, and C

Michael Stone, June 28, 2022, , (src), (all posts)



I’ve been working on a new project named depict, which helps draw pictures of complex systems.

Depict is implemented in Rust and runs both on desktops and on the web.

Making depict run well on the web has been an exciting challenge because of some normally obscure technical details which I’d like better document.


Today’s version of depict relies on a wonderful piece of software called osqp.

Depict uses osqp to figure out, for a given layout or placement of objects to be drawn, how big the objects should be and where they should be positioned.

OSQP is written in (relatively) library-free C.

Now, normally, when you want to combine programs written in multiple compiled languages like Rust and C, you do so either via networking or linking.

Linking, here, means that the compilers used for each separate unit of code to be linked need to agree on how to communicate, which is called an “ABI”, or “application binary interface”.

As it turns out, ABIs are still in flux for WASM – specifically for wasm32.

As of this writing, there are three wasm32 ABIs in common use and one more under active development:

(which uses the wasm-bindgen project’s ABI and runtime glue provided by wasm-bindgen)
(which is still under heavy-development, and needs to be polyfilled on the web, e.g., with wasmer-js)
(which uses emscripten’s/clangl’s ABI and runtime glue provided by emscripten)
(see the WASM component model, see wit-bindgen and Radu Matei’s post: Introduction to WebAssembly components)

(Thanks specifically to lopopolo for pointing out this issue to me, and to the Rust lang-team for documenting the ABI mismatch issue so clearly. More reading: WebAssembly/tool-conventions #88, wasm ABI commit)

On c2rust and wasm32-unknown-unknown

Since I am already targeting wasm32-unknown-unknown, it turned out that the most expedient way to make everything play nicely together was quite unusual: to mechanically translate osqp from C to a drop-in rust replacement that I’m calling osqp-rust using c2rust.

A few more details were required to make all this work.

  1. while osqp is relatively library free, in “non-embedded” mode, it still needs to know whether

    1. to use 64-bit or 32-bit integers (cmake’s -DLONG option) and

    2. it still needs memory allocation as well as OS-specific timing functionality.

    I addressed this by translating osqp twice, once in 32-bit mode and once in 64-bit mode, and then adding by using conditional compilation to select which translation to use at (Rust crate) build time.

  2. memory allocation is supported by rust on wasm32 via the dlmalloc crate, which it turns out is itself a c-to-rust translation of the dlmalloc allocator. Consequently, I was able to (somewhat hackishly) reimplement the necessary APIs as wrappers around rust’s System allocator.

  3. timing is provided on the web by interfaces like Performance.now. Happily, the wasm-bindgen documentation already contains an example of how to use Performance.now so this was also easy to wrap.