Introduction to WebAssembly Text
June 24, 2021
WebAssembly Text (a.k.a. WAT, a.k.a. Raw Wasm) is like an Assembly language for WebAssembly. WebAssembly is a binary format. WAT instructions correspond to a binary opcode in WebAssembly. If you would like to understand what is going on with WebAssembly, how it works, and how to make the most of it, it is beneficial to know WAT. One thing to note about WebAssembly is that it is a Stack Machine. Instead of using registers, WebAssembly pushes values onto and pops values off of a virtual stack. The choice of a stack machine implementation creates smaller file download sizes.
WAT Code
WAT code looks somewhere about halfway between an Assembly language and a high-level language
like JavaScript. In JavaScript, if you had three variable A
, B
,
and C
and you wanted to add the values in A
and B
then
store the result in C
, you would need a line of code that looks like this:
C = A + B;
In WAT, your code would look something like this if:
local.get $a
local.get $b
i32.add
local.set $c
This makes the assumption that the variables $a, $b and $c are all local. Otherwise you
would be using global.get
and global.set
. WAT also has a
requirement that all variable names must begin with a dollar sign ($). The
local.get
statement takes the value in the local variable $a
and
pushes it onto the stack.
What WAT does well
WAT is a lot more limited than most Assembly languages. WAT is not capable of directly performing any input or output. It can not directly manipulate the DOM in a web browser. WebAssembly can make changes to linear memory and make calls to JavaScript functions passed to the module on initialization. Because the calls to JavaScript come with an additional performance cost, you should do your best to limit the number of Wasm -> Javascritpt.
Writing your first WAT module
Let’s write a quick Wasm module in WAT, and we will call it from JavaScript. This
module will take two numbers from JavaScript, add them together, and return the sum.
It’s not too exciting but is a good first step towards a larger world of coding WebAssembly
Text. You’ll need to have Node.js installed to compile your WAT code into a Wasm binary
module. We will use the wat2wasm
assembler I created as a part of the
wat-wasm
npm package. You can install it with the following npm command:
npm i wat-wasm -g
Once you have installed wat-wasm
, you will be able to compile a Wasm module.
Create a file named add.wat
and add the following code to it:
(module
(func (export "sum")
(param $a i32)
(param $b i32)
(result i32)
local.get $a
local.get $b
i32.add
)
)
Modules in Wasm are defined by the code inside the (module)
expression. The
module above has a single function sum
that I defined for external use using
the (export "sum")
. This function takes two 32-bit integer
parameters $a
and $b
. It uses the local.get
command
to load those parameters onto the stack. Then it calls i32.add
which pops two
32-bit integers off of the stack, adds them together and pushes the resulting value back onto
the stack. The function was defined with a (result i32)
expression, so it is
expecting a 32-bit integer to be on the stack when the function is complete. This value is
what will be returned to the calling function, which in this case will be defined in the
JavaScript.
We can compile the module above with the following command:
wat2wasm add.wat -o add.wasm
Calling a Wasm module from JavaScript
Now we can set up a JavaScript module that calls the Wasm sum
function in the
Wasm module. Create a file called add.js and add the following code to it:
const fs = require('fs');
const bytes = fs.readFileSync(__dirname + "/add.wasm");
(async () => {
const obj = await WebAssembly.instantiate(new Uint8Array(bytes));
let value = obj.instance.exports.sum(7, 5);
console.log('7 + 5=' + value);
})();
If you run add.js using node, you should see the following output:
7 + 5=12