Learning Emscripten: Compile C/C++ to JavaScript

It was my first time to hear about WebAssembly when watching Google I/O ‘17. WebAssembly (wasm) lets developers compile C/C++ or other statically typed languages into JavaScript for building high-performance web apps. Assume I have a C/C++ barcode detection or OCR library deployed on server-side,  I can now move it to the web client-side. I was excited about this feature and wanted to know more about relevant technologies. My first step is to learn the compiler that called Emscripten.

What is Emscripten

“Emscripten is an LLVM-based project that compiles C and C++ into highly-optimizable JavaScript in asm.js format. This lets you run C and C++ on the web at near-native speed, without plugins.”

After reading the definition, you may have noticed that it doesn’t mention WebAssembly but asm.js. By default, Emscripten compiles C/C++ code into an asm.js file which is compatible with most of the web browsers. Whereas WebAssembly includes a wasm binary file and a JavaScript glue file:

emscripten diagram

WebAssembly-supported web browsers:

  • Firefox 52+
  • Chrome 57+
  • Latest Opera
  • Firefox 47+: enable the options.wasm flag in about:config
  • Chrome 51+: enable experimental WebAssembly flag in chrome://flags

Download and Installation

Getting Started

Create a hello.c file:

#include <stdio.h>

int main() {

  printf("hello\n");

  return 0;

}

Compile the C/C++ code:

emcc hello.c

There is an a.out.js file generated. We can run it using Node.js:

node a.out.js

emcc compile

To embed the code into a web page, use -o:

emcc hello.c -o hello.html

This command creates a hello.js file and a hello.html file. Open the HTML page in your web browser:

Emscripten run

The a.out.js file and hello.js file are same:

emscripten comparison

Let’s try wasm option to see the difference:

emcc hello.c -s WASM=1 -o hello2.html

Something wrong here:

cannot use WASM=1 when full asm.js validation was disabled (make sure to run in at least -O1, and look for warnings about other options that might force asm.js off).

emcc error

Add -O option to fix the error:

emcc success

This command generates five new files:

emscripten webassembly

If you double-click the hello2.html file in Chrome, you will see nothing:

emcc xhr error

The reason is that hello2.wasm file needs to be loaded via XHR, but Chrome does not support file:// XHR requests. You can use Firefox instead or deploy the page onto a server:

emrun hello2.html

emcc xhr success

Call C/C++ APIs

Emscripten eliminates dead code to minimize code size. Therefore, we have to export the functions that will be called by JavaScript. There are two ways to export native functions.

Export functions by command line

Write C code:

#include <stdio.h>
#include <emscripten.h>

char* world() {
  return "world";
}

int main() {
  printf("hello\n");
  return 0;
}

Compile the code:

emcc -s EXPORTED_FUNCTIONS="['_world']" hello.c -o hello.html

Add following code to hello.html:

<button onclick="native()">click</button>

<script type="text/javascript">
  function native() {
    var content = Module.ccall("world", "string");

    alert(content);
  }
</script>

You can also create an app.js file to run with Node.js:

var hello_module = require("./hello.js");

console.log(hello_module.ccall("world", "string"));

Define the function with EMSCRIPTEN_KEEPALIVE

Change the function:

char* EMSCRIPTEN_KEEPALIVE world() {

  return "world";

}

Compile the hello.c file:

emcc hello.c -o hello.html

When clicking the button, you may see the error message: Assertion failed: the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits).

To fix the issue, add emscripten_exit_with_live_runtime() to main function:

int main() {

  printf("hello\n");

  emscripten_exit_with_live_runtime();

  return 0;

}

Or, you can compile the code with -s NO_EXIT_RUNTIME=1:

emcc hello.c -o hello.html -s NO_EXIT_RUNTIME=1

Preload resource files

Using emcc, we can easily package files to .data file at compile time. Assume you have a license text file located in asset folder. Run the command:

emcc hello.c -o hello.html --preload-file ./asset

Create a new function to read the file:

void getLicense() {
  char license[256];
  int index = 0;
  FILE *file = fopen("./asset/license.txt", "r");
  if (!file) {
    printf("cannot open file\n");
    return;
  }

  while (!feof(file)) {
    char c = fgetc(file);
    if (c != EOF) {
      license[index++] = c;
    }
  }

  fclose (file);

  printf("%s\n", license);
}

Module initialization status

If you want to customize the HTML page, you may have a question about how to tell when the module functions are ready. Take a look at the auto-generated JavaScript code:

setStatus: function(text) {
  if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
  if (text === Module.setStatus.text) return;
  var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
  var now = Date.now();
  if (m && now - Date.now() < 30) return; // if this is a progress update, skip it if too soon
  if (m) {
    text = m[1];
    progressElement.value = parseInt(m[2])*100;
    progressElement.max = parseInt(m[4])*100;
    progressElement.hidden = false;
    spinnerElement.hidden = false;
  } else {
    progressElement.value = null;
    progressElement.max = null;
    progressElement.hidden = true;
    if (!text) spinnerElement.style.display = 'none';
  }
  statusElement.innerHTML = text;
}

If you debug the snippet, you will find when the status turns to `Running’, the module is loaded. It seems a little bit complicated. A better way is to send a notification from main() to JavaScript function:

int main() {
  EM_ASM(onLoaded());
  return 0;
}

Define onLoaded() in custom.html:

<script>
// called from main()
function onLoaded() {
  alert('Module is loaded');
}
</script>

If you do not have a main() function, using onRuntimeInitialized() also works:

<script>
// called when the runtime is ready
 var Module = {
     onRuntimeInitialized: function () {
         alert('onRuntimeInitialized');
     }
 };
</script>

Here is the final custom HTML page:

<!doctype html>
<html>
<head>
</head>
<body>
    <h1>Custom Page</h1>
    <button onclick="native()">click</button>
    <script>
        // called when the runtime is ready
        var Module = {
            onRuntimeInitialized: function () {
                alert('onRuntimeInitialized');
            }
        };

        // called from main()
        function onLoaded() {
            alert('Module is loaded');
        }

        function native() {
            var content = Module.ccall('world', 'string');
            alert(content);
        }
    </script>
    <script async type="text/javascript" src="hello.js"></script>
</body>

</html>

Resources

Source Code

https://github.com/yushulx/asmjs