Home Tutorials Creating and loading a WebAssembly module with Emscripten’s glue code

Creating and loading a WebAssembly module with Emscripten’s glue code [Tutorial]

0
11416
coding screen
10 min read

With simple C code you can test the compiler without having to accommodate for included libraries or WebAssembly’s limitations. We can overcome some of the limitations of WebAssembly in C / C++ code with minimal performance loss by utilizing some of Emscripten’s capabilities.

In this tutorial, we’ll cover the compilation and loading steps of a WebAssembly module that correspond with the use of Emscripten’s glue code.

Learn Programming & Development with a Packt Subscription

The code for this tutorial is available on GitHub. This article is an excerpt from a book written by Mike Rourke titled Learn WebAssembly. In this book, you will learn how to wield WebAssembly to break through the current barriers of web development and build an entirely new class of performant applications.

Compiling C with Emscripten glue code

By passing certain flags to the emcc command, we can output JavaScript glue code alongside the .wasm file as well as an HTML file to handle the loading process. In this section, we’re going to write a complex C program and compile it with the output options that Emscripten offers.

Writing the example C code

Emscripten offers a lot of extra functionality that enables us to interact with our C and C++ code with JavaScript and vice versa. Some of these capabilities are Emscripten-specific and don’t correspond to the Core Specification or its APIs. In our first example, we’ll take advantage of one of Emscripten’s ported libraries and a function provided by Emscripten’s API.

The following program uses a Simple DirectMedia Layer (SDL2) to move a rectangle diagonally across a canvas in an infinite loop. It was taken from https://github.com/timhutton/sdl-canvas-wasm, but I converted it from C++ to C and modified the code slightly. The code for this section is located in the /chapter-05-create-load-module folder of the learn-webassembly repository. Follow the following instructions to compile C with Emscripten.

Create a folder in your /book-examples folder named /chapter-05-create-load-module. Create a new file in this folder named with-glue.c and populate it with the following contents:

/*
 * Converted to C code taken from:
 * https://github.com/timhutton/sdl-canvas-wasm
 * Some of the variable names and comments were also
 * slightly updated.
 */
#include <SDL2/SDL.h>
#include <emscripten.h>
#include <stdlib.h>
// This enables us to have a single point of reference
// for the current iteration and renderer, rather than
// have to refer to them separately.
typedef struct Context {
SDL_Renderer *renderer;
int iteration;
} Context;

/*
* Looping function that draws a blue square on a red
* background and moves it across the <canvas>.
*/
void mainloop(void *arg) {
Context *ctx = (Context *)arg;
SDL_Renderer *renderer = ctx->renderer;
int iteration = ctx->iteration;

// This sets the background color to red:
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
SDL_RenderClear(renderer);

// This creates the moving blue square, the rect.x
// and rect.y values update with each iteration to move
// 1px at a time, so the square will move down and
// to the right infinitely:
SDL_Rect rect;
rect.x = iteration;
rect.y = iteration;
rect.w = 50;
rect.h = 50;
SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
SDL_RenderFillRect(renderer, &rect);

SDL_RenderPresent(renderer);

// This resets the counter to 0 as soon as the iteration
// hits the maximum canvas dimension (otherwise you'd
// never see the blue square after it travelled across
// the canvas once).
if (iteration == 255) {
ctx->iteration = 0;
} else {
ctx->iteration++;
}
}

int main() {
SDL_Init(SDL_INIT_VIDEO);
SDL_Window *window;
SDL_Renderer *renderer;

// The first two 255 values represent the size of the <canvas>
// element in pixels.
SDL_CreateWindowAndRenderer(255, 255, 0, &window, &renderer);

Context ctx;
ctx.renderer = renderer;
ctx.iteration = 0;

// Call the function repeatedly:
int infinite_loop = 1;

// Call the function as fast as the browser wants to render
// (typically 60fps):
int fps = -1;

// This is a function from emscripten.h, it sets a C function
// as the main event loop for the calling thread:
emscripten_set_main_loop_arg(mainloop, &ctx, fps, infinite_loop);

SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();

return EXIT_SUCCESS;
}

The emscripten_set_main_loop_arg() toward the end of the main() function is available because we included emscripten.h at the top of the file. The variables and functions prefixed with SDL_ are available because of the #include <SDL2/SDL.h> at the top of the file. If you’re seeing a squiggly red error line under the <SDL2/SDL.h> statement, you can disregard it. It’s due to SDL’s include path not being present in your c_cpp_properties.json file.

Compiling the example C code

Now that we have our C code written, we’ll need to compile it. One of the required flags you must pass to the emcc command is -o <target>, where <target> is the path to the desired output file. The extension of that file will do more than just output that file; it impacts some of the decisions the compiler makes. The following table, taken from Emscripten’s emcc documentation at http://kripken.github.io/emscripten-site/docs/tools_reference/emcc.html#emcc-o-target, defines the generated output types based on the file extension specified:

ExtensionOutput<name>.js

JavaScript glue code (and .wasm if the s WASM=1 flag is specified).

<name>.html

HTML and separate JavaScript file (<name>.js). Having the separate JavaScript file improves page load time.

<name>.bc

LLVM bitcode (default).

<name>.o

LLVM bitcode (same as .bc).

<name>.wasm

Wasm file only.

You can disregard the .bc and .o file extensions—we won’t need to output LLVM bitcode. The .wasm extension isn’t on the emcc Tools Reference page, but it is a valid option if you pass the correct compiler flags. These output options factor into the C/C++ code we write.

Outputting HTML with glue code

If you specify an HTML file extension (for example, -o with-glue.html) for the output, you’ll end up with a with-glue.html, with-glue.js, and with-glue.wasm file (assuming you also specified -s WASM=1). If you have a main() function in the source C/C++ file, it’ll execute that function as soon as the HTML loads. Let’s compile our example C code to see this in action. To compile it with the HTML file and JavaScript glue code, cd into the /chapter-05-create-load-module folder and run the following command:

emcc with-glue.c -O3 -s WASM=1 -s USE_SDL=2 -o with-glue.html

The first time you run this command, Emscripten is going to download and build the SDL2 library. It could take several minutes to complete this, but you’ll only need to wait once. Emscripten caches the library so subsequent builds will be much faster. Once the build is complete, you’ll see three new files in the folder: HTML, JavaScript, and Wasm files. Run the following command to serve the file locally:

serve -l 8080

If you open your browser up to http://127.0.0.1:8080/with-glue.html, you should see the following:

Emscripten loading code running in the browser

The blue rectangle should be moving diagonally from the upper-left corner of the red rectangle to the lower-right. Since you specified a main() function in the C file, Emscripten knows it should execute it right away. If you open up the with-glue.html file in VS code and scroll to the bottom of the file, you will see the loading code. You won’t see any references to the WebAssembly object; that’s being handled in the JavaScript glue code file.

Outputting glue code with no HTML

The loading code that Emscripten generates in the HTML file contains error handling and other helpful functions to ensure the module is loading before executing the main() function. If you specify .js for the extension of the output file, you’ll have to create an HTML file and write the loading code yourself. In the next section, we’re going to dig into the loading code in more detail.

Loading the Emscripten module

Loading and interacting with a module that utilizes Emscripten’s glue code is considerably different from WebAssembly’s JavaScript API. This is because Emscripten provides additional functionality for interacting with the JavaScript code. In this section, we’re going to discuss the loading code that Emscripten provides when outputting an HTML file and review the process for loading an Emscripten module in the browser.

Pre-generated loading code

If you specify -o <target>.html when running the emcc command, Emscripten generates an HTML file and automatically adds code to load the module to the end of the file. Here’s what the loading code in the HTML file looks like with the contents of each Module function excluded:

var statusElement = document.getElementById('status');
var progressElement = document.getElementById('progress');
var spinnerElement = document.getElementById('spinner');
var Module = {
preRun: [],
postRun: [],
print: (function() {...})(),
printErr: function(text) {...},
canvas: (function() {...})(),
setStatus: function(text) {...},
totalDependencies: 0,
monitorRunDependencies: function(left) {...}
};

Module.setStatus('Downloading...');

window.onerror = function(event) {
Module.setStatus('Exception thrown, see JavaScript console');
spinnerElement.style.display = 'none';
Module.setStatus = function(text) {
if (text) Module.printErr('[post-exception status] ' + text);
};
};

The functions within the Module object are present to detect and address errors, monitor the loading status of the Module, and optionally execute some functions before or after the run() method from the corresponding glue code file executes. The canvas function, shown in the following snippet, returns the <canvas> element from the DOM that was specified in the HTML file before the loading code:

canvas: (function() {
  var canvas = document.getElementById('canvas');
  canvas.addEventListener(
    'webglcontextlost',
    function(e) {
      alert('WebGL context lost. You will need to reload the page.');
      e.preventDefault();
    },
    false
  );
return canvas;
})(),

This code is convenient for detecting errors and ensuring the Module is loaded, but for our purposes, we won’t need to be as verbose.

Writing custom loading code

Emscripten’s generated loading code provides helpful error handling. If you’re using Emscripten’s output in production, I would recommend that you include it to ensure you’re handling errors correctly. However, we don’t actually need all the code to utilize our Module. Let’s write some much simpler code and test it out. First, let’s compile our C file down to glue code with no HTML output. To do that, run the following command:

emcc with-glue.c -O3 -s WASM=1 -s USE_SDL=2 -s MODULARIZE=1 -o custom-loading.js

The -s MODULARIZE=1 compiler flag allows us to use a Promise-like API to load our Module. Once the compilation is complete, create a file in the /chapter-05-create-load-module folder named custom-loading.html and populate it with the following contents:

The loading code is now using ES6’s arrow function syntax for the canvas loading function, which reduces the lines of code required. Start your local server by running the serve command within the /chapter-05-create-load-module folder:

serve -l 8080

When you navigate to http://127.0.0.1:8080/custom-loading.html in your browser, you should see this:

Custom loading code running in the browser

Of course, the function we’re running isn’t very complex, but it demonstrates the bare-bones requirements for loading Emscripten’s Module. For now just be aware that the loading process is different from WebAssembly, which we’ll cover in the next section.

In this article, we looked at compiling C with Emscripten glue code and loading the Emscripten module. We also covered writing example C code and custom code. To know more about compiling C without the glue code, instantiating a Wasm file, installing the required dependencies for the actions performed here, and know build WebAssembly applications, check out the book Learn WebAssembly.

Read next

How has Rust and WebAssembly evolved in 2018

WebAssembly – Trick or Treat?

Mozilla shares plans to bring desktop applications, games to WebAssembly and make deeper inroads for the future web