Programming using Web Assembly
As I described in a previous post, Web Assembly is intended as a target language for compilers, not as a general-purpose language for programmers. In other words, you’re not supposed to write programs in Web Assembly itself but instead write in a higher-level language then compile it into Web Assembly.
I first met assembly languages on 8-bit microcomputers where they were used to write software that ran as fast as possible on what was by modern standards very slow hardware. Since then assembly language has had a special appeal to me, so even though Web Assembly is not intended as an application programming language, I wanted to have a go at programming with it. I coded a simple module that calculates the Mandelbrot Set to be rendered on an HTML canvas.
In this post I take you through the steps I followed:
- how to write a web assembly program by hand using WAT
- how to draw bitmap graphics onto an HTML canvas using Web Assembly and
Web Assembly programs are distributed as a sequence of byte codes in a binary file format called WASM. These files can’t easily be edited in a text editor, so the Web Assembly standard defines an alternative textual representation, called WAT.
To start the project I created a simple WAT file that contains a single function that does nothing more than return the number 1234. It’s called
There is only one actual Web Assembly instruction in the above code :
i32.const 1234. It puts the number 1234 onto a stack; whatever is on the stack when the function ends becomes the function’s return value.
The rest of the file is “pseudo-code” declaring the module, declaring the function and what it returns, naming the function “run” and exporting it so it can be called from outside the module.
The WAT text file needs to be converted to the binary WASM format. The Web Assembly Binary Toolkit project includes a tool to do this, named
The tool is available with a web interface here, but I chose to build and install it myself, then run it locally as
wat2wasm main.wat # This outputs a file named main.wasm
Running the WASM module in a web page
This code uses
python -m SimpleHTTPServer
If you are following along and have entered the above code yourself, you can navigate to http://localhost:8000/ and open Developer Tools, and you should see the result
1234 logged to the console.
Drawing a pixel on a canvas
With the WASM module up and running, I could go ahead and add an HTML canvas. Web Assembly doesn’t have direct access to the DOM API, including the Canvas API, so my WASM code determines what should appear on the canvas by setting pixel colour values in an array, which is then drawn onto the canvas using
Here’s the previous WAT file, modified to import the memory into the module and set a single pixel to a sort of dark plum colour. This has an RGBA value of#880088ff, which in WASM’s little-endian format is the number 0xff880088. (I like to think of it as an “ABGR” value!)
There are three instructions in the above code. The first puts the address of the memory location on to the stack. The second puts the pixel data value on the stack, and the third reads the previous two values off the stack and stores the value in memory.
Uint8ClampedArray and passed to an
ImageData constructor, as in the following example:
Filling the whole canvas
Now that I could draw coloured pixels onto a canvas, I was almost ready to draw something more interesting. Before that, though, I needed a way to loop through all the pixels in the canvas.
A typical loop in WAT looks like this:
.. do some stuff .. ... shall I continue with loop ? ...
b_if 0 ;; The 0 says _where_ to branch to, not _when_ to branch
loop defines the start of a loop block, and
b_if 0 means “branch if the value on the stack is truthy, to the 0th block above this one”.
main.wat after I modified it to loop over all 512 × 512 pixels in the canvas and set them to purple:
This code adds a local variable
$addr declared with the
local pseudo-instruction. In binary WASM files, the variables are referenced as numbers, starting from 0, but WAT lets us write meaningful aliases, like
$addr above. The following would also work:
(local i32) ;; (local $addr i32)
get_local 0 ;; $addr is an alias for 0
The Mandelbrot Set
The foundations were now in place, so I could code something more interesting to look at. I chose the Mandelbrot Set because it uses a simple formula yet gives a spectacular result.
For more information on how the Mandelbrot set is calculated, check out the Wikipedia page, but essentially the code goes as follows:
- Iterate through every memory location.
- Calculate a corresponding real and imaginary (x and y) coordinate for the pixel.
- Repeatedly apply the Mandelbrot formula at that coordinate until either its value grows too large, or a fixed number of iterations have occurred.
- Choose a colour from a predefined palette of 8 colours, depending on the number of iterations that occurred.
The colours for the image are chosen from a hard-coded palette of 8 colours, selected by taking the least significant 3 bits of the iteration count. The palette is defined using
data expressions and stored in memory after the pixel data. A minor change is needed to
main.js to add an extra page of memory for it:
Here is the WAT file in all its glory. Rather than explain each instruction here, I have commented the code, so hopefully, you can see what it’s doing. The algorithm is the escape-time algorithm from the Wikipedia page.
It’s a lot of code compared to the equivalent in a high-level language! But that is the nature of assembly language.
The WAT text format provides an alternative way to format code, in a more compact “s-expression” representation, which groups instructions together on a line using parentheses. I deliberately chose not to use it for this experiment, preferring to keep to a more traditional assembly style, but next time I think I will use the other style to improve readability.
Although there is a lot of code here, the essentials are the same as the previous examples. The only new things are some arithmetic and logic operations (
lt_u), converting between integer and floating point types (
convert_u), and using
datato define the colour palette. I encourage you to consult the Web Assembly documentation to find out more about these instructions if you are interested.
The end result
So there you have it — a Mandelbrot set calculated using Web Assembly and drawn onto an HTML canvas. There’s a lot of scope for improvement, but I found it to be a fun and interesting exercise to introduce myself to Web Assembly. I hope you do too!