DEV Community

Bryan Hughes for Microsoft Azure

Posted on • Edited on

Experimenting with Web Assembly and Node.js

I've been wanting to play around with Web Assembly for a while, and finally had a chance to. Since I'm a mostly Node.js developer, I wanted to play around with it here instead of in the browser. There's not a lof of documentation on Web Assembly though, and the APIs are changing rapidly. I finally got it to work though, and here's how I did it.

I mostly followed the emscripten Getting Started guide, and tweaked it to fit both Node.js and to account for parts of this doc that are out of date.

Installing emscripten

The first step is to install emscripten, which is well documented on emscripten's website. I installed it inside of Windows Subsystems for Linux running Ubuntu, so YMMV if you're using a different OS.

The first step is to install some dependencies. This takes a while, so I recommend brewing a nice cup of tea during this time. I chose a nice Silver Needle this time around :)

# Install Node.js using NodeSource's Ubuntu PPA, if not already installed
curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -

# Install dependencies, as defined in emscripten's getting started guide
sudo apt-get install python2.7 nodejs cmake default-jre
Enter fullscreen mode Exit fullscreen mode

Then we're ready to install emscripten itself. This also takes some time, so enjoy some more tea.

git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
Enter fullscreen mode Exit fullscreen mode

I ran into a bit of an issue with this approach. I don't know if I made a mistake, or things are just locked in. Either way, emscripten installed version 8.x.x of Node.js on it's own and overrode my 10.x.x install of Node.js. Since I'm using async/await for my Node.js code (which requires Node 10 in practice, if not in theory), I needed to manually override this by editing ~/.emscripten so the following line matches the code below:

NODE_JS = '/usr/bin/node' # This line used to have a super long path to the emsdk directory
Enter fullscreen mode Exit fullscreen mode

I never ran into any problems swapping out the version of Node.js that emscripten expected, but as always, YMMV.

Creating the code

First, I created a very simple C file, courtesy of an older blog post at Dynamsoft, named test.c. (Note: the instructions in this blog post no longer work).

int add(int a, int b) {
  return a + b;
}
Enter fullscreen mode Exit fullscreen mode

Then, I created the JavaScript file that consumes this C module:

const fs = require('fs').promises;
const util = require('util');

async function run() {
  async function createWebAssembly(bytes) {
    const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 });
    const env = {
      abortStackOverflow: (err) => { throw new Error(`overflow: ${err}`); },
      table: new WebAssembly.Table({ initial: 0, maximum: 0, element: 'anyfunc' }),
      __table_base: 0,
      memory,
      __memory_base: 1024,
      STACKTOP: 0,
      STACK_MAX: memory.buffer.byteLength,
    };
    return WebAssembly.instantiate(bytes, { env });
  }

  const result = await createWebAssembly(new Uint8Array(await fs.readFile('./output.wasm')));
  console.log(util.inspect(result, true, 0));
  console.log(result.instance.exports._add(9, 9));
}
run();
Enter fullscreen mode Exit fullscreen mode

A few things to note about this JavaScript code. First, the code is a masheup of the code from the Dynamsoft blog post and the emscripten getting started guide. I recommend reading the emscripten getting started guide for an in-depth explanation of what's going on here. The next thing to note is the __table_base and __memory_base entries. These are named tableBase and memoryBase in the emscripten documentation and in almost all other documentation I've found, but crashes when run. I'm guessing the API changed very recently. Hat tip to this gist for pointing me in the right direction.

Running the code

Now that we've written our files, it's time to run the code. First we compile this code with the following command:

emcc -s WASM=1 -s ONLY_MY_CODE=1 -s EXPORTED_FUNCTIONS="['_add']" -o output.js *.c
Enter fullscreen mode Exit fullscreen mode

This command will generate the file output.wasm that's referenced in the JavaScript code. Now we're ready to run our code!

> node index.js
{ instance: Instance [WebAssembly.Instance] {},
  module: Module [WebAssembly.Module] {} }
18
Enter fullscreen mode Exit fullscreen mode

And it works! We can indeed add 9 and 9 together to get 18, calling from JavaScript to C++ and back again, all using Web Assembly!

My next step is to take the C++ messaging stack from my LED synchronization and animation project, wrap it in Web Assembly so Node.js can talk to my boards, and integrate it with Azure IoT Edge so I can control my LEDs from the cloud!

Top comments (15)

Collapse
 
adam_cyclones profile image
Adam Crockett ๐ŸŒ€

Do I need the emscripten glue code?

I am seeing errors like:
LinkError: WebAssembly.instantiate(): Import #1 module="env" function="_emscripten_get_heap_size" error: function import requires a callable

Collapse
 
sudhanshu16 profile image
Sudhanshu Gautam

Hey! I am experiencing the same issue. I have used a 'c' code.
Do you know the solution to this issue?

Collapse
 
adam_cyclones profile image
Adam Crockett ๐ŸŒ€

Use emscripten and include the JavaScript gluecode and you won't have this issue.

Collapse
 
nebrius profile image
Bryan Hughes

Just to make sure I'm reading this correctly (the timestamps aren't totally clear to me), did your comment on my other post indicate you figured this out?

Collapse
 
adam_cyclones profile image
Adam Crockett ๐ŸŒ€

Correct, yes your other post helped me figure this part out.

I feel like I am 90% there with my experiment.

Collapse
 
nebrius profile image
Bryan Hughes

I also learned that if you're compiling C++ code with em++ instead of emcc, make sure to include extern "C" in front of any functions in your C++ code that you want to access from JavaScript. Otherwise, you'll get errors that the function can't be found (cause of C++'s complicated name mangling rules).

Collapse
 
adam_cyclones profile image
Adam Crockett ๐ŸŒ€

Is this why my exported functions are found in c but when I switch to cpp, my functions are undefined. I just refactored in rage for hours.

Collapse
 
nebrius profile image
Bryan Hughes

I bet that's it...if for no other reason because I was also refactoring in rage for some time too before I figured it out!

Thread Thread
 
adam_cyclones profile image
Adam Crockett ๐ŸŒ€

Oh man you are absolutely right, I am so happy! that is one of my many questions solved. seems like emscripten can only access C atleast with the similar code that you have. I'm trying to get Lua into node. Its proving ... challenging.

Collapse
 
splugenbrau profile image
SplugenBrau

How long these directions will last valid ??
I mean, DynamSoft site is one year old and is already obsolete.
Must bet Node.js will kill PHP but why learn emscripten now?
And let consider HTML5. Why not learn also?
Where are Webmasters go? Who know?
And CGI is obsolete?
My website rely still on an NGINX system !!!
I just have a headache.
Bye.

Collapse
 
nebrius profile image
Bryan Hughes

Soooo...I just learned that apt-get has an emscripten package. You can install it with sudo apt-get install emscripten and I'm assuming it works the same. I haven't tested it yet though.

Collapse
 
apihlaja profile image
Antti Pihlaja

With fastly moving things like this, you probably don't want to use that method for development setup. For the same reasons you don't install Node.js from default repo.

Collapse
 
nebrius profile image
Bryan Hughes

Agreed, and that is in fact what I ended up discovering.

Collapse
 
johannesvollmer profile image
Johannes Vollmer

Haha, well, launching an entire browser just to run some C is not something you see every day ๐Ÿ˜

Collapse
 
nebrius profile image
Bryan Hughes

Good thing I'm not launching a browser ๐Ÿ˜‰