On this page

Introduction

What is WAT? It is the programming language used with Web Assembly. It is a text representation of the internal binary format of a WASM file. You can also see it as a low level code similar to Assembly.

When should you use it? Normally you would use other programming languages that compile down to WASM. However, there are times when the task you want to perform is relatively small, and you want the maximum speed possible, so writing it in WAT would be the perfect solution.

Who is this for? If you want to learn how to program in WAT, how to integrate it into JavaScript and you want to see a full reference list of different instructions, then this is the place for you.

We will not be looking at how Web Assembly works inside the browser, or how other programming languages can be compiled down into WASM code, we are only focusing on the WAT programming language and how to use it with JavaScript.

Web Assembly

JavaScrip is one of the most well known and versatile programming languages available. It can be used in the browser and on servers in the cloud. Great though it may be, it has one big problem, it is a scripting language and as a result, it is not very fast.

Programming languages like C/C++ get compiled down into machine executable code that runs as close to the hardware as possible. This makes them use as much of the computing power that's available. JavaScript, however, requires extra steps, needs loading, translating and the code executing, none of which is handled at machine code level, making it slower.

This is all fine most of the time, but there are occasions when your code needs to work as quickly as possible. To improve performance, to make your code run at machine code speeds, something new was needed and Web Assembly was created to do just that.

Web Assembly can be seen as the lowest level of code you can go for web based applications. It is a set of low level code that instructs the computer to perform different low level steps, similar to machine code, but in a machine independent way. They are processed on the machine in a much quicker way, and can even be compiled into machine code, so that it can run as quickly as possible.

Getting Started

Before you can start programming in WAT you will need some tools. Below are some of the ones you need and some others that will help.

WAT2WASM

To convert a WAT file into a WASM file, you will need the wat2wasm application. This can be found in the GitHub repository “WABT: WebAssembly Binary Toolkit”, which you can find here https://github.com/WebAssembly/wabt. Before you can use it though, it will need to be built, using CMAKE. This can be a bit of a challenge, so good luck.

You can also find other tools and information there which you may find useful. The only tool we will be using is the wat2wasm application. You can use the --help switch to view all the command options available.

Visual Studio Code

If you are using Visual Studio Code then I recommend using the “WebAssembly” extension. This will highlight WAT file syntax. It can be used to convert a WAT file into WASM, but I do not recommend it, as it does not give any error messages when something is wrong within your WAT code.

First WAT

Below is an example of a simple WAT application.

(module
  (func (export "add") (param $first i32) (param $second i32) (result i32)
    ;; Push the $first and $second parameter value on to the stack
    local.get $first
    local.get $second

    ;; Pops the last two items off the stack, add them together, and
    ;; pushes the result onto the stack
    i32.add
  )
)

It looks very different to anything you may have seen before, but don't worry about the syntax for now, you'll learn to understand it in time. As you have properly guessed, there is a single function that takes two integer numbers, adds them together and returns the result.

To convert it into a WASM file we need to run the wat2wasm tool. This is a command line application that needs to be run with command switches, to control what input file it should be looking at, and what output file it needs to create. You can use Visual Studio Code's terminal tab to run the following command.

To compile the WAT file into a WASM file call the following command.

wat2wasm add.wat

If there aren't any errors then it will create the add.wasm file.

Run with JavaScript

To get the WASM file loaded into the browser and to be able to run the functions inside, will take a number of steps. Here is an example that uses the add.wasm file and the add function inside it, to add two random numbers together.

<body>
  <p>Example of the a WASM add function.</p>
  <p>
    <b>$first:</b> <span id="first"></span> <br>
    <b>$second:</b> <span id="second"></span> <br>
    <b>result:</b> <span id="result"></span>
  </p>
  <button id="set-random-numbers">Set Random Numbers</button>
  <button id="add-numbers">Add Numbers</button>
</body>
// Load in and create instance of add.wasm file
const wasm = await WebAssembly.instantiateStreaming(fetch('add.wasm'));

// Set random numbers click event
document.getElementById('set-random-numbers').addEventListener(
  'click', () => {
  // Give the first and second elements some random number values
  document.getElementById('first').textContent =
    (Math.random() * 1000).toFixed(0);
  document.getElementById('second').textContent =
    (Math.random() * 1000).toFixed(0);
});

// Add numbers click event
document.getElementById('add-numbers').addEventListener('click', () => {
  // Get the first and second parameter values
  const first = parseInt(document.getElementById('first').textContent);
  const second = parseInt(document.getElementById('second').textContent);

  // Call the WASM add function
  const result = wasm.instance.exports.add(first, second);

  // Show the result on the page
  document.getElementById('result').textContent = result.toString();
});

The first thing you need to do is to make sure the WASM file is reachable and can be downloaded from the browser.

You then need to take the WASM file data and call the WebAssembly instantiateStreaming function, with a call to fetch the WASM file, to create a new instance of it, ready to be used.

// Load in and create instance of add.wasm file
const wasm = await WebAssembly.instantiateStreaming(fetch('add.wasm'));

Once we have an instance of the WASM file, you can call any of its functions.

// Call the WASM add function
const result = wasm.instance.exports.add(first, second);

Here is an example you can play around with. Open it up and debug it to see what is happening.

Run with NodeJS

To use WASM within NodeJS is very similar to using it in the browser. The only difference is the way we load in the WASM file data. In the browser you need to fetch it over a HTTP connection, but in NodeJS you need to load it in as a file, using the fs read functions.

Take a look at this small application that uses the same add.wasm file as before.

import { argv } from 'node:process';
import { exit } from 'node:process';
import { readFile } from "node:fs/promises";
import path from 'path';
import { fileURLToPath } from 'url';

// Check arguments
if (argv.length !== 4) { console.log('Requires two numbers.'); exit(0); }

// Get current folder
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
        
// Read WASM data
const wasmBuffer = await readFile(__dirname + '/add.wasm');

// Create WASM data from buffer
const wasmData = new Uint8Array(wasmBuffer);

// Instantiate the WASM data
const wasm = await WebAssembly.instantiate(wasmData);

// Get parameters
const first = parseInt(argv[2]);
const second = parseInt(argv[3]);

// Call the WASM add function
const result = wasm.instance.exports.add(first, second);

// Log the results
console.log('First = ' + first.toString());
console.log('Second = ' + second.toString());
console.log('Result = ' + result.toString());

If you use NodeJS to run this application, with the two number argument parameters, then it will add them together and show the result.

Let's take a closer look at what is going on here. The first thing we do is try to workout where the WASM file is located. In this example we are assuming that it is in the same folder as the application source file nodejs-add.js.

// Get current folder
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

We can then use the file location to load in the WASM file data. This is then turned into an array of unsigned 8 bit integers.

// Read WASM data
const wasmBuffer = await readFile(__dirname + '/add.wasm');

// Create WASM data from buffer
const wasmData = new Uint8Array(wasmBuffer);

// Instantiate the WASM data
const wasm = await WebAssembly.instantiate(wasmData);

The WebAssembly instantiate function can then be used to create an instance of the WASM module. From here we can call any of the WASM functions and interact with it just like we do on the browser.

Debugging

It is important you know how to debug your WAT code. There will come a time when something is going wrong and you will need to look inside to see what is happening, moving through your code line by line, and seeing the value of every variable being used. This can be done in both the browser and in NodeJS.

You will need to use DevTools on the browser. In the source tab you can see all the files used by the page, which also includes the WASM file (after it has been loaded). Selecting the file will show you the inner WASM code. This is not the WAT source code you have been editing, but the compiled WASM data shown in WAT notation. It can look different, with labels given different names, like $var0 instead of $first.

You can add a breakpoint within the WASM file, which stops the execution and allows you to perform a number of debugging tasks. For example, adding a breakpoint on the i32.add instruction and calling the add function, you will be able to see the parameter values and the stack array containing all the items with their values. You can step through line by line seeing what happens. You will not be able to adjust any of the values within the debugger though.

When it comes to debugging in a NodeJS application you are a little more limited. Each time a WASM file is loaded in, a new temporary WAT file is created that contains the WASM data as WAT, which allows you to step through it, but because it changes each time, adding breakpoints inside does nothing.

What you need to do is add a breakpoint in the JavaScript code that calls the WASM function and then step into the WASM code with the debugger. Once inside the temporary WAT file you can add breakpoints, to allow you to move to the area you want. You do get the same information from the debugger as you do in the browser. You can see all the parameter values and view the stack with all the items and their values. You can step through the code each line at a time and slowly watch the application perform its tasks.

You can build your WASM files to include the names of the parameters and local variables by using the command switch --debug-names when using the wat2wasm tool.

More to See

We have taken a quick look at WASM and WAT. Now is where the real fun starts. We will look at every aspect of the WAT programming language to understand what it can do and how to create modules that run at low level machine code speeds.

Below are some of the areas we will be looking into.

Basics

Looking at the basic parts you need to understand so that you make a start creating your own WASM modules.

Functions

Create your own functions, add parameters, return values, and export them into JavaScript.

Numbers & Bitwise Operations

Manipulate numbers in a variety of different ways. Use the basic math functions and perform bitwise changes on them.

Conditions

Understand how to use if...then...else statements to control the flow of execution.

Loops

Learn how to perform loops, repeating the same block of code for a given number of times.

Globals

Shared data between functions, between instances or enough between different WASM modules.

Memory

Explore how memory is created, manipulated by both JavaScript and WASM, and even see how the same memory can be shared between different WASM instances.

Tables

Use callback functions to help create reusable libraries.

Exceptions

Learn to handle errors and even create your own type of exceptions to throw.

Examples

Explore a number of examples that use WAT to perform different tasks. See how things are put together to create solutions to real world problems.

Reference

See the complete list of all the S-Expressions and instructions that can be used within your WAT application.