Embedding emscripten in a Node.js library
Tags: javascript, node, webassembly, question
Published: 2019-03-15 20:42:27

---
title: Embedding emscripten in a Node.js library
published: true
description: Web Assembly promises to change no less than the future of how we develop JavaScript applications, but we're still early in the life of WASM. APIs are changing, and little focuses on Node.js. Here's how I got it working
tags: javascript,node,wasm,question
series: Adventures in Web Assembly
---

I've been experimenting with Web Assembly lately, and right now I'm in the early stages of getting the [messaging stack](https://github.com/nebrius/RaverLightsMessaging) of my [wireless LED control system](https://github.com/nebrius/raver-lights) running in Node.js via WASM. I've gotten it up and running (yay!), but it's pretty hacky right now (boo!).

## The Scenario

My library is written in C++. I intentionally avoided using anything from the C++ Standard Library and instead used the C Standard Library exclusively (it's more portable and I suspect less complicated to compile). Either way though, both of these standard libraries are _runtime_ libraries that need to be compiled in to the output. If you're a JavaScript developer and have no idea what I'm talking about, imagine if jQuery or Underscore/Lodash were defined as part of the JavaScript spec and were bundled with Node.js, but were still separate libraries. That's the C/C++ Standard Libraries.

All C++ compilers come with these libraries built-in, and you don't have to do anything special to use them. emscripten comes with implementation for these as well, but IME they're still tricky to use. You have to change the compile flags. Specifically, you have to remove the `-s ONLY_MY_CODE=1` flag that I mentioned in my [previous blog post on WASM](https://dev.to/azure/experimenting-with-web-assembly-and-nodejs-40f4). If this is the only change you make to everything in that post, your app will crash with the following:

```
[LinkError: WebAssembly Instantiation: Import #1 module="env" function="nullFunc_ii" error: function import requires a callable]
```

Uhm, excuse me, what exactly is `nullFunc_ii` and why should I care?

So here's what's going on, to the best of my understanding. emscripten compiles your code and injects any and all runtime libraries necessary to run a C++ application. This includes the aforementioned standard libraries, but also includes some other things. Most notably, emscripten injects some runtime libraries to handle things like stack overflows, exception handling, segfaults, etc. I'm about 75% certain that the `nullFunc_xxx` methods are part of the latter.

These methods are all defined in _JavaScript_, not C++, and so are not included in the `output.wasm` file. Instead, they're included with a JavaScript runtime file called `output.js` (given my `-o` flag value).

## My Hacky Solution

So how did I get around this? My first step was to check out the emscripten docs on [output files and formats](https://emscripten.org/docs/compiling/Building-Projects.html#emscripten-build-output-files) and the various [emscripten specific config flags](https://github.com/emscripten-core/emscripten/blob/master/src/settings.js).

As far as I can tell, what I want to do isn't possible. emscripten allows you to either compile code on it's own (via the `-s ONLY_MY_CODE=1` flag), or to compile a complete application that includes a `void main() {}` (i.e. _not_ a library). Uhmmm...ok...?

After a lot of trial and error, I found a _really hacky_ solution that seems to work.

First, here's my complete compile command I'm now using (note: you can ignore the `ERROR_ON_UNDEFINED_SYMBOLS` part, I'll talk about that in a later post):

```bash
em++ -s WASM=1 -s ERROR_ON_UNDEFINED_SYMBOLS=0 -s EXPORTED_FUNCTIONS=\"['_init','_loop','_setWaveParameters']\" -std=c++11 -Isrc -g4 -o js-dist/output.js js/*.cpp
```

Note how we have `-o js-dist/output.js` in the command. This tells emscripten to generate a JavaScript runtime file. This file is _intended_ to be used as a "main" file, i.e. an _entire_ application. We want to use it as a library though, not an application. There are a lot of things in here we need though, most notably two variables it creates called `asmGlobalArg` and `asmLibraryArg`. These variables define all of the `nullFunc_xxx` methods, among others. These variables are not exported in any way though, and as far as I can tell are not meant to be consumed directly.

We're going to do it anways, damn the consequences! I wrote a script to automatically hack this file with the following contents:

```javascript
const { readFileSync, writeFileSync } = require('fs');
const { join } = require('path');

const OUTPUT_FILE = join(__dirname, '..', 'js-dist', 'output.js');

let source = readFileSync(OUTPUT_FILE).toString();

source = source.replace('var asmGlobalArg =', 'var asmGlobalArg = module.exports.asmGlobalArg =');
source = source.replace('var asmLibraryArg =', 'var asmLibraryArg = module.exports.asmLibraryArg =');

writeFileSync(OUTPUT_FILE, source);
```

Now we can import these variables into our main file (which is now written in TypeScript FWIW):

```typescript
import { readFile } from 'fs';
import { join } from 'path';

import { asmGlobalArg, asmLibraryArg } from './output';

let wasmExports: WebAssembly.ResultObject | undefined;
const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 });

readFile(join(__dirname, 'output.wasm'), (readErr, buf) => {
  const bytes = new Uint8Array(buf);
  const env = {
    ...asmLibraryArg,
    table: new WebAssembly.Table({
      'initial': 192,
      'maximum': 192,
      'element': 'anyfunc'
    }),
    __table_base: 0,
    memory,
    __memory_base: 1024,
    STACKTOP: 0,
    STACK_MAX: memory.buffer.byteLength
  };
  const global = {
    ...asmGlobalArg
  };
  WebAssembly.instantiate(bytes, { env, global })
    .then((result) => {
      wasmExports = result;
      wasmExports.instance.exports._init(deviceId);
    })
});
```

And it works! But it's also pretty ugly IMO. I'm excited though, cause I'm one step closer to integrating this system with [Azure IoT Edge](https://docs.microsoft.com/en-us/azure/iot-edge/?WT.mc_id=devto-blog-brhugh) so I can control my LEDs from the cloud!

## Question for all of you

Am I missing something here? Is there a proper way to do this? I find it hard to believe that emscripten doesn't have an option for compiling a library that includes all the necessary runtime libraries, but I can't seem to figure out how.