Hit the Ground Running with WebAssembly 🚀

A tutorial on using WebAssembly with Emscripten and C/C++ (even if you don’t know any C/C++)

JavaScript — which was famously designed in 10 days way back in 1995 — has until very recently remained the only language you could use natively on the web. It wasn’t until 2017 that WebAssembly 1.0 was released, making it the second language supported by all major web browsers.

What is WebAssembly?

This is how beautiful your desk will look once you start using WebAssembly (photo by Luca Bravo / Unsplash)

What’s all the fuss about?

Incidentally, this is also why WebAssembly can be very useful outside the browser, as we’ll discuss later on.

Let’s write some WebAssembly!

Step 1: Set up your environment

Although installing Emscripten from scratch can take some time, I made a Docker image you can use to simplify this process:

# Fetch image containing Emscripten
$ docker pull robertaboukhalil/emsdk:1.38.30
# Create a local folder for our WebAssembly adventures
$ mkdir ~/wasm
$ cd ~/wasm
# Launch a container called "wasm" (in the background)
$ docker run -dt --name wasm \
--volume ~/wasm:/src \
-p 12345:80 \
robertaboukhalil/emsdk:1.38.30

The --volume option allows us to run Emscripten inside a container and retrieve the generated .wasm files directly from ~/wasm (i.e. no need to transfer files from the container to the host!). Note that we also expose the container’s port 80 as port 12345, and I’ve set up the container image such that it will automagically launch a web server so you can directly browse the .html files output by Emscripten, as we’ll see below.

Phew, we’re now ready to use WebAssembly! 😅

Step 2: Compile code to WebAssembly

Let’s go inside the container and fetch the code:

# Go inside the container we created above
$ docker exec -it wasm bash
# Now we're inside the container!
$ git clone https://github.com/pod32g/MD5.git

Inside the MD5 folder, you’ll see an md5.c file:

$ ls
README.md md5.c

You can see the full code here, but here’s pseudocode to illustrate how the code is structured:

# md5.cfunction md5(myString):
return some_complicated_math(myString)
function main(argv):
# Expect user to provide a string as input:
# argv[0] = name of executable
# argv[1] = string to hash
if length(argv) < 2:
print("usage: %s 'string'\n", argv[0])
return 1
# Calculate and output the hash
hash = md5(argv[1])
print(hash)

Essentially, main() fetches the input string provided by the user and calls the md5() function on that string.

Setting WebAssembly aside for a second, if we wanted to compile md5.c to good-old binary, we would do:

$ gcc -o md5 md5.c

Since Emscripten provides wrappers around tools like gcc, compiling to WebAssembly is simply:

$ emcc -o md5.html md5.c

All we did was to use Emscripten’s gcc wrapper called emcc (Emscripten also provides wrappers for g++, make, cmake and configure called em++, emmake, emcmake and emconfigure, respectively). We also ask Emscripten for a .html output file instead of a binary.

Behind the scenes, Emscripten creates the compiled WebAssembly file md5.wasm, but it will also generate md5.js and md5.html, which are the boilerplate code we need to automatically initialize our WebAssembly module.

Step 3: Running our code in the browser

By default, Emscripten’s glue code calls the main() function on page load. Since it calls main() with no arguments, however, the output is usage information. Also note that Emscripten sets argv[0] to "this.program".

To test our new tool, open the developer’s console and type the following to compute the md5 hash of the string test:

> Module.callMain([ "test" ])
098f6bcd4621d373cade4e832627b4f6

Module.callMain() is a utility function provided by Emscripten’s glue code (md5.js), which calls our C program’s main() function with a list of arguments (in our case, just one). In the process, callMain() also converts the string test into an array of integers (since Wasm only understands ints and floats!).

Step 4: A simple application

Here’s a simple app.html file to illustrate the idea. When the page is loaded, it asks the user for a string, runs our WebAssembly code on it, and outputs the MD5 hash to the user. To do so, we’ll extend the Module object I mentioned above — the details are beyond the scope of this article, but the comments should give you a sense for how this works:

<!-- app.html --><script type="text/javascript">
var Module = {
// Don't run main() on page load
noInitialRun: true,
// When Wasm module is ready, ask user for a string
onRuntimeInitialized: () => {
let myString = prompt("Enter a string:");
Module.callMain([myString]);
},
// Redirect stdout to alert()
print: txt => alert(`The MD5 hash is: ${txt}`)
};
</script>
<script src="md5.js"></script>

You can now launch http://localhost:12345/MD5/app.html and hash a string of interest:

After we enter a string to hash, we get an alert box with the MD5 hash of that string!

And voilà! In just a few steps, we ported an off-the-shelf utility from C to WebAssembly.

Granted, this was a simple example: we have a single C file with simple inputs, where we only expose the main() function, require no file system support, no graphics of any kind, and no heavy computations. That said, hopefully this example gives you a glimpse of how code is compiled to WebAssembly.

⚠️ Caution: Shameless plug ahead️ ⚠️

If you’re ready to dive into WebAssembly in a lot more depth (and how to port much more complex codebases like awk or Pacman), check out my book Level up with WebAssembly, a practical and approachable guide to using WebAssembly in your own web applications 😊

Real-world use cases

1) Use WebAssembly to surgically speed up your web applications

If a library that performs the data analysis you need already exists, and is written in C/C++/Rust, WebAssembly is the clear choice. In such cases, it’s not worth spending the effort to port the code to JavaScript in the first place (and then proceed to validate and optimize it!).

Resources:

  • Case Study: The app 1Password achieved order-of-magnitude speedups by using WebAssembly
  • Case Study: How we sped up a web app by 20X by replacing slow JavaScript calculations with WebAssembly
  • Blog Post: Discussion of the overhead of running WebAssembly code, and why we should be careful with “micro-benchmarks”

2) Port desktop apps and games to WebAssembly

Resources:

  • Case study: How AutoCAD used WebAssembly to port their 30-year-old codebase to the web without having to rewrite everything from scratch 😅
  • Demo: Play a clone of Doom3 in the browser, built with WebAssembly
  • Tutorial: How to port a simple Asteroids game (written in C) to the web

3) Port command-line tools to the web

Resources:

  • Code: SQLite running in the browser!
  • Tutorial: How to build a playground for the jq command line tool

4) WebAssembly outside the browser

But if we want to productively run WebAssembly code outside the browser, how do we support features like files, threads, and sockets? The answer is that we need some sort of standard interface that WebAssembly can use to communicate to the outside world to request resources. To address that, there is an ongoing standardization effort around the WebAssembly System Interface (or WASI) that aims to make this possible.

We’re still in the early days of running WebAssembly outside the browser (WASI was announced just a few months ago in March 2019), but as you’ll see in the resources below, we live in exciting times!

Resources:

  • Blog Post: Detailed introduction to WASI, how it works, and why it’s useful
  • Demo: Run WebAssembly binaries on the command line using Wasmer
  • Tutorial: Using Wasmer’s Go library to execute WebAssembly programs
The universal sign for “I’m about to talk about cloud computing” (photo by Alex Machado / Unsplash)

5) Serverless WebAssembly

Resources:

  • Tutorial: Serverless Rust with AWS Lambda and WebAssembly
  • Tutorial: Running serverless WebAssembly on Cloudflare workers
  • Blog Post: How Fastly runs WebAssembly at the edge to significantly improve runtime

Closing remarks

Of course, it won’t always make sense to use WebAssembly — shocking, I know — so you should evaluate whether the benefits it brings are worth the added complexity.

I hope this article has made WebAssembly feel a little less like magic 🧙, and a little more like a powerful tool in your toolbox.

If you enjoyed reading this and want a practical guide to get started with WebAssembly, check out my book Level up with WebAssembly!

Bioinformatics Software Engineer, Author of Level up with WebAssembly book.