logo

A Quick Dive Into WebAssembly

As of late, I’ve become really interested in WebAssembly (Wasm). If this is your first time hearing about Wasm then I hope this will be a solid introduction into the Wasm ecosystem. I’ll be focusing on writing code in this post, for a deep dive into the inner workings of Wasm, Rasmus Andersson wrote a great post about it here.

Wasm defines itself as a:

“binary instruction format for a stack-based virtual machine. Wasm is designed as a portable target for compilation of high-level languages like C/C++/Rust, enabling deployment on the web for client and server applications.”

In short and from what I understand, it is a way to compile languages such as C/C++/Rust onto the browser. These languages are strongly typed, multi-threaded, and will be able to give apps near-native speed.

How do I write code in Wasm?

You don’t! You write your code in C/C++/Rust and compile your code to Wasm. The most popular toolchain to compile these lower level languages to Wasm is by using Emscripten.

Since there are a multitude of languages, I’ll be giving examples in C/C++ for this post.

Getting Started

Like previously mentioned, to compile C/C++ code into Wasm, you’ll need to download and install Emscripten. Once you have that installed it’s easy to get a “Hello, world!” by running the following on your terminal. The following example is copy-pasta from the Wasm getting started page.

mkdir hello
cd hello
cat << EOF > hello.c
#include <stdio.h>
int main(int argc, char ** argv) {
  printf("Hello, world!\n");
}
EOF
emcc hello.c -s WASM=1 -o hello.html

Then to serve those files to a browser: emrun --no_browser --port 8080 .

And navigate your browser to http://localhost:8080/hello.html

There you go! Your first C to Wasm program.

A slightly more complicated C example

I’m looking to render something to the screen, after all, one of my biggest interests about Wasm is its ability to turn OpenGL code into WebGL. So the goal is to make a rotating triangle onto the screen using a C module, in this case we’ll use glfw.

The rotating triangle example is an example from the glfw documentation, but in this implementation we’re going to have give it some added code to make sure that it is able to be ran with Emscripten.

Here’s a link to the repo where you can find all the source code and follow along with me. I won’t go over all the code but I can go through the important parts and the parts specific to Wasm. Let’s start by looking at the main.c file located inside src/, where we find the following:

#include <emscripten/emscripten.h>

This #include will give you access to a couple of essential functions that you need to be able to use Emscripten to compile your C code to Wasm. The most important for our example is:

emscripten_set_main_loop(em_callback_func func, int fps, int simulate_infinite_loop);

This takes in a C function as the main event loop for the calling thread. What this means is that this function’s first argument can render something to the screen. In our case we want to use this function to render our triangle onto the screen.

static void generate_frame()
{
  float ratio;
  int width, height;
  mat4x4 m, p, mvp;
  glfwGetFramebufferSize(window, &width, &height);
  ratio = width / (float)height;
  glViewport(0, 0, width, height);
  glClear(GL_COLOR_BUFFER_BIT);
  mat4x4_identity(m);
  mat4x4_rotate_Z(m, m, (float)glfwGetTime());
  mat4x4_ortho(p, -ratio, ratio, -1.f, 1.f, 1.f, -1.f);
  mat4x4_mul(mvp, p, m);
  glUseProgram(program);
  glUniformMatrix4fv(mvp_location, 1, GL_FALSE, (const GLfloat *)mvp);
  glDrawArrays(GL_TRIANGLES, 0, 3);

  glfwSwapBuffers(window);
  glfwPollEvents();
}

int main() {
    emscripten_set_main_loop(generate_frame, 0, 0);
}

Our generate_frame() function is responsible for all that is rendering. So logically we’d want to pass that into our emscripten_set_main_loop function, the other two parameters deal with frames-per-second, fps , and if we want to simulate an infinite loop. The Emscripten docs say that if our function wants to render something that we should set fps to 0 because then the compiled code will use the browsers requestAnimationFrame mechanism to control the main loop. Since we have the frame already going in an infinite loop then we can set the third argument as 0 for false.

Much of the other logic inside our main.c file is specific to drawing and rotating the triangle (glfw is a whole other post). So feel free to copy the other logic inside the file and paste it in, including the linmath.h file. So now we should just have the linmath.c and main.c file under a src folder and we are ready to get into a browser!

Run the following:

emcc src/main.c  -O3 -s USE_WEBGL2=1 -s FULL_ES3=1 -s USE_GLFW=3 -s WASM=1       -o build/index.html

wOaH, wut does all that mean?

Let’s break it down, emcc is the base emscripten compile command, src/main.c is our target file, -03 is weird because it follows multiple similar commands that all optimize for Javascript but -03 is specified as one that is good for production but you can read more about them here, -s because we want Javascript for every module, USE_WEBGL2=1 FULL_ES3=1 USE_GLFW=3 are built in functions to say that we do use and need these modules when we are compiling our code, WASM=1 means that we want Emscripten to compile our code to Wasm because the default is to compile to asm.js, and finally -o build/index.html is saying to output our compiled code to build/index.html.

Hol-e cow that was long, but now you can run python -m SimpleHTTPServer 8080 and navigate over to http://localhost:8080/build/ to see our spinning triangle!

Now, let’s be good devs and add a nice Makefile at the root. Makefiles make it easy to compile processes in C/C++.

CC = emcc
SRCS = main.c
FILES = $(addprefix src/, $(SRCS)) # Add 'src/' to each source
OBJS = $(FILES:.c=.o) # Modify file extensions of FILES
EOPT = USE_WEBGL2=1 FULL_ES3=1 USE_GLFW=3 WASM=1 # Emscripten specific options
EOPTS = $(addprefix -s $(EMPTY), $(EOPT))   # Add '-s ' to each option

.PHONY: dist clean

# Builds necessary files
build: $(OBJS)
        mkdir -p build
        $(CC) $(FILES) -O3 $(EOPTS) -o build/index.html

# Removes object files, but leaves build for serving
dist: build
        rm -f $(OBJS)

# Cleans up object files and build directory
clean:
        rm -rf build/index.html build/index.js build/index.wasm
        rm -f $(OBJS)

That makes things easier, now we can simply write three commands to compile and do all of our heavy lifting:

  • Build: make
  • Clean: make clean
  • Build, but remove objects leaving the build dir: make dist

Thanks for reading and if you have any questions or comments, feel free to tweet me @tfaieta.