Paul Galvin

@pagalvin

Web Assembly Proof of Concept With Emscripten & VS Code

The TL;DR: I’ve been interested in Web Assembly for a while and finally sat down to do a little proof of concept to see how far I could get. I’m happy and encouraged with the result.

You might find this useful for your own research.

Waxing Historical

If you just want to get to the technical bits, skip this section.

I’m increasingly excited about the potential for writing web apps in languages other than JavaScript. This is already happening, of course. I haven’t written anything of consequence in plain JS since … I’m not sure. I distinctly remember writing an app using plain JS back in 2013(!) and that’s it.

Since then, I’ve moved on to working with TypeScript. I enjoy TS a lot (I recently published a free book on it), but at the end of the day, it’s mostly just JavaScript and it doesn’t do quite enough to ameliorate my SmallTalk envy :). I’ve played around a bit with Closure but I got sidetracked from that with this *hilarious* article about systems programmers. You should really just got read it now.

Seriously. Just go read it.

I’m a self-taught C programmer who learned from the first edition of Kernighan and Ritchie’s seminal book (first edition, no less). I bought and used Borland’s C++ compiler waaay back, like 20 years ago. I’m very far away from those roots these days :(. Don’t get me wrong — I love what I do, but there’s something really fun and thrilling about working so directly with memory and being close to the hardware like that.

So, that funny article got me thinking about those old fun days and I decided to see how viable it would be to write C/C++ code and deploy it as part of a web application.

The POC

I got started by looking up “web assembly tutorial” and came across this article: https://tutorialzine.com/2017/06/getting-started-with-web-assembly

This article largely worked but I think that some of the underlying infrastructure bits have changed. I had to make a few changes on the JavaScript side to get that article’s code to work. I then extended it a bit.

I did all of this work on a windows 10 laptop.

This is what happens at a high level:

  • You generate some Wasm code. Wasm is a binary format that some browsers understand. As I write this, Chrome can load and work with WASM. Others can too, but I only worked with Chrome.
  • You can generate Wasm binaries various ways. I chose to use Emscripten. I think there are other options.
  • Emscripten will take your C or C++ code, compile it and generate the Wasm binary for you. I’m sure it won’t take just any C/C++ code. I have no idea what those boundaries might be.
  • Emscripten also generates a boat load of JavaScript. The author of the TutorialZine article refers to it as “glue code.” Among other things, this generated JS knows how to load and boot up your Wasm binary file. It also provides a number of life cycle hooks for you to glom onto. The load and initialize process runs asynchronously. I used one of the hooks to announce (via a plain JS event) that my C code was ready for business.
  • Once it’s loaded, you call into your C/C++ routines just like they are plain JS functions. Your C code can invoke your JavaScript functions and you can even have your C code call a JS function that invokes another C function. Why do that? Well, for the pure pleasure of it, of course!

Step 1: Installing Emscripten

I found this to be quite easy and not very long. Go here: https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html.

Use the web installer.

Wait a while and you’re done.

Step 2: Write some C code

I used the TutorialZine code as a basis for my testing and added a bit:

As you can see, there’s a regular C main() function on line 8.

There are three additional functions:

  • roll_dice: This is from the TutorialZine article. It generates a random number from 1 to 6. The JS UI calls into this when the user clicks on a CSS-rendered die and re-draws itself based on the result.
  • Note how there’s a token, EMSCRIPTEN_KEEPALIVE. I’m not entirely sure how this works but … it basically keeps the function around post-load. If you don’t decorate your function with that token, you can’t call it from JavaScript.
  • I have a function, sayHi(). This one does a couple of things:
  1. At line 19: emscripten_run_script(“console.log(‘hi3!’)”); That emscript_run_script executes that associated JavaScript.
  2. At line 20, it invokes the function, _sayXyzzy(). _sayXyzzy is actually another function in the C program that just logs out to the console, “xyzzy” (again, via emscripten_run_script — see line 24 in the code).

It’s not doing much, but it’s good enough for this baby-steps POC.

Compile the C code

I started off by doing this via the Emscripten command line (part of the web installer) and it’s pretty simple:

You can always tell those developers who come from the ee cummings branch of CompSci.

The first time I ran the command, it did a bunch of initialization and there were some scary warnings. However, it all worked out. I think it’s actually compiling a bunch of standard C libraries, possibly downloading them first. Subsequent runs look like the screenshot above.

You should read the full Emscripten docs for a detailed run-down on the options. That command is compiling a single C file, dice-roll.c. It generates a WASM file (WASM=1). It does very little optimization (-O1 — note, it’s O for Optimize, no 0 for Zero). It also generates that glue JS file and names it “index.js”.

I switched over to using VS Code soon after that. VS Code helpfully told me there was a plugin for viewing C/C++ files and I installed that. I then created a task for the above. I ended up creating a .bat file with the actual command. My tasks.json looks like this:

Making It Work in the Browser

It’s pretty simple to get this to work in Chrome. Just create a barebones .html file (like index.html) and include that index.js file the emcc command generated in the prior step.

That index.js file is … something :). In my env, it’s 6,111 lines long :). It’s not *super* well documented, but was enough to help me figure out. This is a good place to start: https://kripken.github.io/emscripten-site/docs/api_reference/module.html#module.

Among 1,000 or so other things, it loads the Wasm file and creates a globally available object, Module. In fact, it supplements Module if you already created it. I think you’d always want to do that so that you can influence the initialization life cycle.

This index.js plus Module object allowed me to do several important things:

  • Load the Wasm in the first place
  • Add a function to the Module’s “postRun” array. The index.js initialization code runs these postRun functions once the Wasm is loaded and initialized.
  • Provides a useful wrapper that lets me invoke the C code functions.

Here’s my HTML:

A lot of this is straight from that TutorialZine article. I’ve modified by adding a reference to “prerun.js”.

Emscripten created that index.js file (line 18) and that’s the 6100 line glue monster.

The rest of this is from TutorialZine. It references some CSS that does a good job showing a die. It adds an event listener on click, dispatching an anonymous function that call out to the C code at line 31:

var result = Module._roll_dice();

If you recall, the actual C function’s name is roll_dice (no leading underscore). The Emscripten index.js file populates the Module object with all of your C functions (remembering they have to be decorated with that EMSCRIPTEN_KEEPALIVE tag). These are, in essence, proxy JavaScript functions. Emscripten inserts an underscore in front of the C function’s name. If you have function, “someAwesomeFunction” in your C code, the JS Module object will have a function, _someAwesomeFunction() for you to invoke when you want.

Line 21 actually throws a runtime error. That _sayHi() JS function, which is a proxy to the sayHi() function in C doesn’t exist yet. The index.js file does its work asynchronously. It is eventually available and by the time I get around to clicking on the die, it does. Line 33 never fails but line 21 always fails.

I obviously want to know when the C code is ready to use. This is where the “prerun.js” comes into play.

Here’s the prerun.js that gets loaded from line 17:

A line 1, I define an empty Module object.

I then define a couple of functions (sayAbc(), sayXyz(), sayInit()).

When the index.js glue code executes, it will supplement the Module object if it already exists or create a new one. In this case, I’ve already created it, so it supplements it.

In fact, it does more than supplement it. I can influence the Module life cycle by providing some functions for it to execute at certain times, like preRun[], onInit[], postRun[] and maybe more. The online docs led the way here and postRun[] was where I was able to reliable start running my C code.

You can see that I have a function, notifyReady() at line 18. It just creates an Event and publishes it onto the window object.

At line 25, I have an event listener. When it picks up the event, it logs out a happy message and then invokes Module._sayHi().

I added two functions to the postRun[] array on line 23.

Summarizing

When you spell it all out in detail like this, it might feel a bit overwhelming, but it’s actually not that complicated.

  1. Install Emscripten.
  2. Write some C code.
  3. Have some kind of build process to emcc your C code. There are a million ways to do this, of course.
  4. Use the generated JS “glue” code to load and process your Wasm.
  5. Take advantage of the lifecycle array hooks to safely execute your C functions.

Once I got the basic tooling done, it was a piece of cake. Edit my C file, press control-shift-B. Wait for that and then press F5 on the browser.

(Quick note on the browser — you need to use a real web server since the Wasm files are fetch()’d / XHR’d. Chrome won’t let you can’t fetch a file from the file system via the file:// protocol, so you need a real web server to do this. I use Fenix for these kinds of things — it’s ridiculously easy to work with.).

And that’s it! It feels like a pretty viable thing to try at this point. I am not entirely sure where to go next. I think I might try implementing a fancy sorting algorithm and do some compare/contrast with that and plain JS.

<end/>

<postscript>

I recently published a book on TypeScript! It’s free and you can access it here: https://www.gitbook.com/book/pagalvin/yet-another-typescript-book/details

</endForReal>

More by Paul Galvin

Topics of interest

More Related Stories