Betting on Wasm

We’re a small team (currently 3) building Shareup and we’re trying to accomplish a lot with a little. We need to make some bets on technologies and practices which can help us move faster and build better software with less. One important technology we are betting on is Wasm and I’d like to tell you more about it.

Wasm, or WebAssembly, is a few things: an assembly-like language, a binary format, a standard computer specification, and an ecosystem of tools and libraries. Code compiled to Wasm runs safely and at near-native speed in a browser. Wasm binaries can be executed outside of the browser too thanks to projects which have implemented the WebAssembly specification.

The WebAssembly homepage has a nice overview of the objectives of the WebAssembly specification if you want to dig into the nerdy details.

What is assembly?

Assembly is programming code that is pretty close to what a computer understands. It’s still a programming language for humans and it still needs to be compiled to “machine code,” but it’s pretty darn close to what is sent to the CPU of a computer. CPUs have very few instructions they can be asked to perform – WebAssembly specifies which instructions the standard Wasm virtual machine needs to implement.

If you want to learn more about WebAssembly and Assembly then checkout the article WebAssembly: How and why.

Why Wasm?

The Wasm community has many ideas for how Wasm can be used: the Rust community is very excited about running Rust in the browser, some serverless solutions are hoping to use Wasm to make it easier to safely run third-party code, some want to recompile existing programs to run on new architectures or new platforms, along with many more.

The idea of having a standard, identical computer whose only job is running through the assembly instructions we’ve compiled from our code is exactly what we’ve wanted for a long time. The Wasm computer is fully spec’ed out regarding how its linear memory works, how the function table is organized, and how to process the stack instructions from the single binary .wasm file.

With Wasm we can write code once and be confident it will run exactly the same every time everywhere. This is helpful for code we consider mission-critical. Code for transforming binary representations, diffing text or data structures, and other low level data protocols are good candidates for WebAssembly. Before, writing and testing our logic on different platforms was time-consuming and error-prone. Now we can write once and ship wherever. If this sounds too good to be true, well, there are some cons, and I’ll detail them further down, but we’ve seen positive results.

Write Rust and run everywhere

A very large portion of the online discourse around Wasm is about running Rust in the browser and on the server. Check out Rust and WebAssembly, the Rust Wasm Book, and wasm-pack which is the primary tool for compiling Rust to Wasm. We like Rust, but we are not currently “a Rust shop.” We’ve been keeping an eye on Rust and Wasm and doing some experiments. We plan to continue and keep track of the exciting Rust + Wasm community and continue to regularly experiment with Rust and Wasm.

Safely download and update binaries from the internet

Since executing Wasm code is safe and sandboxed it can be used as a way to safely download and update code from the internet to be run on a device or server. There are examples of downloading new software for arduino devices and I’ve talked to a few developers who want to use Wasm for delivering over the air updates to desktop and mobile software. We don’t have this use case for Shareup, but it is interesting to follow and learn about.

Safely execute third-party code for serverless products

Cloudflare in particular has been pushing WebAssembly Workers as a safe way to deploy sandboxed serverless programs written in many languages onto their edge cloud network. Right now most Wasm on the server is run inside a Node process, but in the future I believe all serverless platforms will support Wasm directly like Fastly’s Terrarium because of how quickly a Wasm VM boots compared to a container.

We have some server code utilizing the wasmex library from a phoenix application to quickly spin up and run some of our shared code on our servers. While our use case isn’t “serverless” per-se, we are spinning up a new Wasm VM for every request with elixir processes so it’s pretty close to a home grown serverless, uh, service.

Compile and run any existing program in the browser

Emscripten seems to be the primary toolkit for compiling and running existing programs in the browser. We are not interested in running any existing programs in the browser, so it’s not immediately as useful for us.

Most tools like emscripten generate both a .wasm binary along with a Javascript wrapper and we’ve seen both of those outputs be pretty large and sometimes not well-functioning without manual edits to the output. It’s not really something we would consider serving to browsers or trying to include in our native apps. Still, there are tons of articles and examples about this use case online and it might be something you are into, especially if you want to run games in the browser.

Write code specifically targeting Wasm to share and run on many platforms and devices

Our use case for Shareup is to write some mission critical code once, ship that binary to every platform including the browser, and have it run identically everywhere. We are writing code directly targeting Wasm, compiling it only to Wasm, and running it with the most appropriate Wasm runtime for each platform. We’ve setup our tooling to take a bunch of source files and produce a single .wasm file that we can then ship to all our platforms including browsers.

Wasm also offers us an easy way to integrate our shared code into various platforms. We can write our shared code in whichever language we prefer and the resulting .wasm file can very easily be integrated into a program for iOS, Android, macOS, Windows, and browsers. We could then potentially change our mind and use a different language in the future (say rewrite from C to Rust) without having to change our applications and how they integrate with the Wasm binary at all. We get a lot of flexibility without an increase in integration complexity.

We are seeing very predictable performance on every platform so far. We can be super confident our code works exactly the same on every platform which is important for some of our most important business logic for secure sharing.

What are the cons?

The first major con to using Wasm is the various communities and languages all have different priorities and focuses. It can sometimes be difficult to find information related to our use case buried underneath the deluge of articles being written about all the different uses for Wasm. We’ve had to put in some work and compile our own list of important articles and documentation to keep ourselves informed and up to date about the parts of the Wasm community which affect us most directly.

The second major con is that sometimes tooling like emscripten, wabt, clang + llvm, wasmer, wasm3, etc can be difficult to use, undocumented, or incomplete. As much as we can, we are trying to give back and send pull requests to fill in any gaps with the tools we are using.

And the third major con is it is difficult to debug a Wasm program. One doesn’t see a normal stack trace when something goes wrong. Wasm doesn’t have exceptions yet – so a program terminates if there is an issue. And depending on the Wasm runtime and platform combination, things can be pretty darn opaque. Debugging for Wasm programs is getting better all the time and since Wasm runtimes are fully spec’ed out and very similar, one can be confident that code running in one Wasm runtime (like the browser) will work well in another (wasmer, for example).

Betting on Wasm allows us to build more with less

We are betting Wasm will let us spend less time writing the same CPU intensive algorithm in different languages and instead focus on the different layers and experiences which are unique per platform. We believe Wasm allows us to be confident that we can write once and run everywhere in a safe sandbox with predictable performance. While it still feels pretty early for Wasm and there are some cons, we are very excited to invest in it and the larger community.

More to come…

Over the next two weeks I’ll walk you through the different languages which compile to Wasm, demonstrate how to compile some C code to Wasm, and I’ll write about some conventions we’ve come up with to help us organize and keep our code sane when dealing with Wasm on multiple platforms. ✌️