Skip to content

Instantly share code, notes, and snippets.

@kanaka
Last active June 17, 2021 21:39
Show Gist options
  • Save kanaka/3c9caf38bc4da2ecec38f41ba24b77df to your computer and use it in GitHub Desktop.
Save kanaka/3c9caf38bc4da2ecec38f41ba24b77df to your computer and use it in GitHub Desktop.
Run wast (WebAssembly) in node

Run wast (WebAssembly) in node

This is a simple example of compiling and running the WebAssembly textual format (wast) in Node.js on Linux. This does not cover compiling from a higher level language to WebAssembly (for that you probably want Emscripten). But if all you want to do is play around directly with the wast textual format, this should get you going quickly.

Links

Prerequisites

  • Install node 8

  • You need a wast to wasm compiler. The are several options. This example uses wabt (wabbit). You will need a standard gcc toolchain to build wabt. Build wabt and add it to your path:

git clone --recursive https://github.com/WebAssembly/wabt/
cd wabt
make gcc-release
export PATH=`pwd`/out/gcc/Release

Compile and Run Wast Code

Compile a wast to wasm (binary module):

wast2wasm addTwo.wast -o addTwo.wasm

Run an exported function in the wasm binary module using the runwasm.js script (this should print 5 to the console):

node ./runwasm.js addTwo.wasm addTwo 2 3

The runwasm.js script is also written to be used as a node module:

node
> w = require('./runwasm.js')
> w.loadWebAssembly('addTwo.wasm').then(i => console.log(i.exports.addTwo(7,8)))
15

You are free to use runwasm.js code under an MIT license.

You can also run wasm modules using wac (WebAssembly in C). wac is a WebAssembly interpreter in which the code runs in a native x86 host context rather than in a JavaScript/Web host context.

wac addTwo.wasm addTwo 2 3
0x5:i32
(module
(func $addTwo (param i32 i32) (result i32)
(i32.add
(get_local 0)
(get_local 1)))
(export "addTwo" (func $addTwo)))
#!/usr/bin/env node
// Copyright Joel Martin
// License MIT
const fs = require('fs'),
assert = require('assert')
assert('WebAssembly' in global,
'WebAssembly global object not detected')
// Convert node Buffer to Uint8Array
function toUint8Array(buf) {
var u = new Uint8Array(buf.length)
for (var i = 0; i < buf.length; ++i) {
u[i] = buf[i]
}
return u
}
// Based on:
// https://gist.github.com/kripken/59c67556dc03bb6d57052fedef1e61ab
// and
// http://thecodebarbarian.com/getting-started-with-webassembly-in-node.js.html
// Loads a WebAssembly dynamic library, returns a promise.
// imports is an optional imports object
function loadWebAssembly(filename, imports) {
// Fetch the file and compile it
const buffer = toUint8Array(fs.readFileSync(filename))
return WebAssembly.compile(buffer)
.then(module => {
// Create the imports for the module, including the
// standard dynamic library imports
imports = imports || {}
imports.env = imports.env || {}
imports.env.memoryBase = imports.env.memoryBase || 0
imports.env.tableBase = imports.env.tableBase || 0
if (!imports.env.memory) {
imports.env.memory = new WebAssembly.Memory({ initial: 256 })
}
if (!imports.env.table) {
imports.env.table = new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
}
// Create the instance.
return new WebAssembly.Instance(module, imports)
})
}
if (module.parent) {
module.exports.loadWebAssembly = loadWebAssembly
} else {
assert(process.argv.length >= 4,
'Usage: ./runwasm.js prog.wasm func INT_ARG...')
const wasm = process.argv[2],
func = process.argv[3],
// Convert args to either floats or ints
args = process.argv.slice(4).map(
x => x.match(/[.]/) ? parseFloat(x) : parseInt(x))
loadWebAssembly(wasm)
.then(instance => {
var exports = instance.exports
assert(exports, 'no exports found')
assert(func in exports, func + ' not found in wasm module exports')
//console.log('calling exports.'+func+'('+args+')')
console.log(exports[func](...args))
})
.catch(res => {
console.log(res)
})
}
@tiye
Copy link

tiye commented Jun 9, 2017

Would you be interested in turning this Gist into a repo like this? https://github.com/minimal-xyz

@aduh95
Copy link

aduh95 commented Apr 21, 2018

Thank you for sharing this.

FYI, the function toUint8Array is not usefull, because fs.readFileSync returns already a Uint8Array (Buffer is a "subclass" of Uint8Array).

$ node -p "require('fs').readFileSync('/dev/null') instanceof Uint8Array"
true

So I was able to refector your script to be more efficient on Node 10:

const [_, __, wasm_file, func_name, ...args] = process.argv;

import("fs/promises")
  .then(module => module.default.readFile(wasm_file))
  .then(bytes => WebAssembly.compile(bytes))
  .then(
    module =>
      new WebAssembly.Instance(module, {
        env: {
          memory: new WebAssembly.Memory({ initial: 256 }),

          table: new WebAssembly.Table({
            initial: 0,
            element: "anyfunc",
          }),
        },
      })
  )
  .then(instance => instance.exports[func_name](...args))
  .then(console.log, console.error);

To run it, use node --experimental-modules ./wasmQuickRun.mjs ./add.wasm addTwo 45 -3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment