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:
wasm32-unknown-unknown
wasm32-wasi
wasm32-unknown-emscripten
wasm32-???-???
(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)
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.
while osqp is relatively library free, in “non-embedded” mode, it still needs to know whether
to use 64-bit or 32-bit integers (cmake’s -DLONG
option) and
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.
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.
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.