Interacting with javascript from WebAssembly

Update: As with my previous post, this is now also updated to match the current (october 2017) version of web assembly.

In my previous blog post I covered the basics of WebAssembly and how to export functions that operate on either integer or floating point values. This is fine if we are doing simple number crunching, but most real world tasks require access to browser APIs. This is possible, but unfortunately we can only import functions accepting and returning a fixed amount of integer and floating point values. Thus, the simple task of printing a string, actually require us to be a little creative, but let’s start with the basics.

Importing functions

Let us create a WebAssembly module that can count the number of digits in a base ten integer. To do this, we increment the number by one, take its log10 and round it up to nearest integer (for simplicity we’ll ignore the cases where the number is less than one). WebAssembly has instructions for all this, except calculating the log10 of a number, and rather than implementing this ourselves, we’ll borrow the one from the javascript Math library.

We need two function signatures. Our digits function accepting and returning a 32-bit integer and the log10 function accepting and returning a 64-bit floating point number. In the previous blog post we learned how to declare type signatures, functions, and how to export them, so I will skip this. If you really can’t figure this out, feel free to disassemble the final bytecode below.

To make WebAssembly aware of external functions we list them in the import section. The section contains a variable length array of import declarations, where an import declaration consists of two variable length byte arrays containing a utf-8 encoded name of a module and function, a byte indicating the kind of import (0x00 for functions) and (for function imports) a variable length integer referencing the signature of the function. Adding the following section will import the log10 function from the math module.

0x02 // import section
0x0e // the size of the rest of the section
0x01 // number of functions to import
0x04 // the size of the module name
0x6d 0x61 0x74 0x68 // “math”
0x05 // the size of the function name
0x6c 0x6f 0x67 0x31 0x30 // “log10”
0x00 // function import
0x01 // the index of the signature

We also need to tell WebAssembly where to find the math module, but more on that later.

Once a function is imported it behaves just as a regular WebAssembly function. It will have an index starting from zero, where functions defined inside the module will have an index starting after the last imported function. Thus, our log10 function will have index 0 and our count function will have index 1. Actually calling it is just a single instruction (0x10) followed by the index of the function. It will take arguments from the stack (so make sure they are there and in the right order) and put the result back on the stack.

0x0a // code section
0x10 // the size of the rest of the section
0x01 // the number of function bodies
0x0e // the size of the first function body
0x00 // the number of local variable definitions
0x20 0x00 // load the first argument onto the stack
0xb7 // convert the i32 on top of the stack to a f64
0x41 0x01 // Load the constant 1 onto the stack
0xb7 // convert the i32 on top of the stack to a f64
0xa0 // sum the top two elements on the stack
0x10 0x00 // call the 0th function
0x9b // round the top element on the stack up to nearest integer
0xaa // convert the f64 on top of the stack to a i32
0x0f // return
0x0b // end

To make the log10 function available to WebAssembly, we provide an additional parameter when instantiating the module. This will expose a math module containing all the functions included in the javascript Math module (even though we are only referencing one of them). The final javascript program is

WebAssembly.instantiate(new Uint8Array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x0b, 0x02, 0x60, 0x01, 0x7f, 0x01, 0x7f,
0x60, 0x01, 0x7c , 0x01, 0x7c, 0x02, 0x0e, 0x01,
0x04, 0x6d, 0x61, 0x74, 0x68, 0x05, 0x6c, 0x6f,
0x67, 0x31, 0x30, 0x00, 0x01, 0x03, 0x02, 0x01,
0x00, 0x07, 0x0a, 0x01, 0x06, 0x64, 0x69, 0x67,
0x69, 0x74, 0x73, 0x00, 0x01, 0x0a, 0x10, 0x01,
0x0e, 0x00, 0x20, 0x00, 0xb7, 0x41, 0x01, 0xb7,
0xa0, 0x10, 0x00, 0x9b, 0xaa, 0x0f, 0x0b
]), {
'math': Math
}).then(function(wasm) {
wasm.instance.exports.digits(42); // returns 2
});

Contiguous memory

All this is very nice, but what do we do if we need something more interesting … like outputting text? The good news is that it is possible, the bad news is that it is not as straightforward as you would hope.

In order to make more complex data structures available, like strings, arrays, or objects, we must place them in the contiguous memory and let javascript read and interpret them from there. When working with contiguous memory, we need to introduce two new sections; the memory section and the data section.

The memory section describes memory segments, and each segment specifies the amount of memory available (in pages of 64KB), using a variable length integer specifying the initial amount of memory, and optionally a variable length integer specifying the maximum amount of memory (in case we want to grow it during execution). These are prefixed with a single byte indicating whether the optional maximum is present or not.

0x05 // memory section
0x03 // the size of the rest of the section
0x01 // the number of memory segments
0x00 // the optional maximum is not present
0x01 // allocate one block of 64KB

You can initialize the memory by adding a data section. You can add arbitrary chunks of data of any size at any location in any segment, as long as it fits inside the memory you allocated in the memory section. The section contains a variable sized array of data chunks, each described by a variable length integer indicating the memory segment to use, bytecode instructions calculating the offset where the data is placed, and finally a variable length array of bytes containing the actual data. The instructions allowed when calculating the offset is currently quite limited, and in most cases you will probably just need the sequence below, loading a 32-bit integer constant (0x0b is the end marker indicating that no more bytecode will follow).

0x0b // data section
0x13 // the size of the rest of the section
0x01 // one chunk of data
0x00 // in the memory section at index 0
0x41 0x2a 0x0b // instructions loading the constant 42
0x0d // the size of the following data
// "hello, world!"
0x48 0x65 0x6c 0x6c 0x6f 0x2c 0x20 0x77 0x6f 0x72 0x6c 0x64 0x21

The above sections is enough to work with the memory from inside Web Assembly, but if we want to access it from javascript we also need to export it … just like functions. The only difference is that the type is 0x02 instead of 0x00.

0x07 // export section
0x07 // the size of the rest of the section
0x01 // one export
0x03 // the size of the exported name
0x6d 0x65 0x6d // “mem”
0x02 0x00 // export first memory segment

We are now finally ready to leave WebAssembly and define the function that can print out our string from javascript. If we want to import it in WebAssembly it can only have integer or floating point parameters, so in this case the function will take two arguments (a position and a length) and use these to look up the data, convert it into a javascript string, and output it to the console.

var memory = null;
function print(index, length) {
var bytes = Array.prototype.slice.call(memory, index, index + length);
var msg = bytes.map(function(byte) {
return String.fromCharCode(byte);
}).join('');
console.log(msg);
}

When instantiating the module, we need to tell WebAssembly where to find the print function, and tell javascript where to find the contiguous memory.

WebAssembly.instantiate(new Uint8Array(bytecode), {
‘io’: {
‘print’: print
}
}).then(function(wasm) {
memory = new Uint8Array(wasm.instance.exports.mem.buffer);
});

Notice, by the way, that the exported memory contains an ArrayBuffer that you cannot read or manipulate directly. Instead you can create a view of it, like we have done above using the Uint8Array to get access to the underlying bytes. You can actually have multiple views on the same ArrayBuffer, allowing you to easily convert between different values as long as they are aligned, like

var buffer = new ArrayBuffer(4);
var uint32View = new Uint32Array(buffer);
var float32View = new Float32Array(buffer);
uint32View[0] = 1078530011;
console.log(float32View[0]); // Outputs 3.1415927410125732

After importing the print function from the io module, we can export a greet function containing the following instructions.

0x41 0x2b // load the i32 value 42 onto the stack
0x41 0x0d // load the i32 value 13 onto the stack
0x10 0x00 // call function 0 (the imported one)

The final javascript looks as follow

var bytecode = [
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x09, 0x02, 0x60, 0x02, 0x7f, 0x7f, 0x00,
0x60, 0x00, 0x00, 0x02, 0x0c, 0x01, 0x02, 0x69,
0x6f, 0x05, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x00,
0x00, 0x03, 0x02, 0x01, 0x01, 0x05, 0x03, 0x01,
0x00, 0x01, 0x07, 0x0f, 0x02, 0x05, 0x67, 0x72,
0x65, 0x65, 0x74, 0x00, 0x01, 0x03, 0x6d, 0x65,
0x6d, 0x02, 0x00, 0x0a, 0x0b, 0x01, 0x09, 0x00,
0x41, 0x2a, 0x41, 0x0d, 0x10, 0x00, 0x0f, 0x0b,
0x0b, 0x13, 0x01, 0x00, 0x41, 0x2a, 0x0b, 0x0d,
0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77,
0x6f, 0x72, 0x6c, 0x64, 0x21
];
var memory = null;
function print(index, length) {
var bytes = Array.prototype.slice.call(memory, index, index + length);
var msg = bytes.map(function(byte) {
return String.fromCharCode(byte);
}).join('');
console.log(msg);
}
WebAssembly.instantiate(new Uint8Array(bytecode), {
'io': {
'print': print
}
}).then(function(wasm) {
memory = new Uint8Array(wasm.instance.exports.mem.buffer);
wasm.instance.exports.greet();
});

A note on the start section

You may have noticed that WebAssembly mentions a section called start. This section contains only a single variable length integer referencing a function to run when the module is instantiated.

To run our greet function at load time we could add the following start section to our module

0x08 // start section
0x01 // the size of the rest of the section
0x01 // the function to run

This wouldn’t work though. Since we don’t have access to the contiguous memory from javascript until the module is fully instantiated, we cannot call javascript functions that rely on access to the memory while instantiating.

So what’s next

By now we know all the essential parts of WebAssembly, except for what bytecode instructions are available and how to use them. I have kept the examples simple so far, to make them more digestible, but my next blog post we will be all about the bytecode.