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++)

Robert Aboukhalil
Jun 17 · 9 min read

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?

As the name implies, you can think of WebAssembly as an Assembly language for the Web (though we’ll see later why it’s also useful outside the browser). As with other Assembly languages you’ve heard about in CS101, no one actually writes code directly in WebAssembly — except Ben Smith 😉. Instead, us mere mortals generally use it as a “compilation target”, i.e. you take code written in other languages (like C, C++ and Rust), compile that code to WebAssembly, and run it in the browser.

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

What’s all the fuss about?

WebAssembly is a Big Deal™ because, unlike other technologies (like the JVM), it’s a standard born out of a collaboration between all major browser vendors, and it was designed with the sandbox model of the web in mind. That is what makes WebAssembly so compelling.

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


Let’s write some WebAssembly!

As our “Hello World” program, we’ll find some off-the-shelf C code for calculating the MD5 hash of a string, compile it to WebAssembly, and run it in the browser!

Step 1: Set up your environment

A popular tool used to compile code from C/C++ to WebAssembly is Emscripten. It’s powerful because it provides a lot of very useful features to make our lives easier when porting code to WebAssembly. This includes wrappers for C/C++ compilers, auto-generated JavaScript boilerplate code for running our WebAssembly code, a virtual file-system (for code that needs to read/write files), and access to a host of pre-ported libraries (such as zlib or SDL).

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

For our MD5 hash example, we’ll use this repo: https://github.com/pod32g/MD5.

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

If you followed the instructions above for creating the wasm container, you should be able to launch http://localhost:12345/MD5/md5.html in your browser and see something like this:

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

Of course in real life, we don’t ask the user to open the dev console, so how do we use WebAssembly in our apps?

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

Now that we’ve seen our “Hello MD5 World” example, let’s explore real-world use cases for WebAssembly.

1) Use WebAssembly to surgically speed up your web applications

You can sometimes use WebAssembly to replace slow JavaScript computations and speed up your web applications. This is possible because WebAssembly is a typed language, features a linear memory structure, and is stored in a compact binary format which is faster to download and interpret than JavaScript.

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

Although you can sometimes get away with not having to make changes to a codebase to port it to WebAssembly (as was the case for our MD5 example), you’ll almost always have to when it comes to games and other graphical applications. This is because, although most games are written as infinite loops that wait for user input (move the mouse, press a key, etc), infinite loops don’t play nicely in the browser, where they block the main thread and can crash your browser tab. Luckily, Emscripten provides a slew of useful functions to get around this limitation, which you can use to essentially simulate infinite loops by calling the same function at a regular interval.

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

Beyond porting graphical applications, we can also use WebAssembly to port command-line tools to the web! Why would you ever do that? One reason would be to reuse an existing feature that is already efficiently implemented (e.g. if you wanted to show differences between two files, you could reuse the diffing algorithm included in the diff command line tool). Or you could build an interactive playground that allows users to test out (and learn about) the tool without first having to install it.

Resources:

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

4) WebAssembly outside the browser

Despite the “Web” in its name, WebAssembly’s usefulness reaches well beyond the browser. In fact, you can use it as a JVM-like way to run a .wasm binary on any platform, provided that platform supports a WebAssembly runtime, such as Wasmtime or Wasmer.

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

Another use case for using WebAssembly outside the browser is to use it as part of a serverless/function-as-a-service architecture. One benefit is that by supporting WebAssembly, cloud providers would be indirectly supporting a lot more languages, including C, C++, and Rust, which are not usually directly supported by serverless cloud providers. Moreover, the sandboxed nature of WebAssembly means it is more amenable to running multiple independent modules from different clients in parallel within the same process/container, which results in much faster function initialization times.

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

Although we’re still in the early stages, WebAssembly is already being used in real-world applications, whether to port entire CLIs/games/desktop apps to the web, applying it surgically to speed up front-end computations, or even using it outside the browser. Looking to the near future, WebAssembly will gain support for threads, SIMD instructions, garbage collection, and the ability to directly manipulate the DOM, just to name a few.

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!

Robert Aboukhalil

Written by

Bioinformatics Software Engineer

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade