This post was originally published on my Medium blog and was later picked up for publication by DevGenius
In my previous post of this series, we had briefly looked at a byte-by-byte breakdown of WebAssembly’s binary format. Armed with that knowledge, let’s have a look at the textual format for WebAssembly. This will be captured in a human-readable file with a .wat extension.
As with the binary format, the basic (and most simple) unit for the textual format is a module as well. An empty (and very valid).wat file is as shown below.
However, this doesn’t do a lot of things… except declare a module.
But… as we already know from the previous post, a WebAssembly module is one big S-expression. To recap, S-expressions are basically a way to represent data as a nested tree. As a matter of fact, the module declaration above is only a standalone root. Various child nodes with attributes get added as the complexity of your program increases, effectively grouping all of the functions (and data) under the root.
A pictorial depiction of what I just mentioned has been provided below.
This brings us to the question,
What does a function look like in WebAssembly?
( func <signature> <locals> <body> )
Since it is written as an S-expression, I’m sure it looks alien at an initial glance. However, on decomposing the above pseudocode structure, we see that
- signature corresponds to what a function would take as an input and return as values
- the body is composed of a low-level list of instructions
But before we write our very first function body in .wat, we need to talk about one teeny thing: stack machines.
If you visit webassembly.org (or even the Wikipedia page for WebAssembly), one of the first things you notice is the mention of a stack-based virtual machine. In a stack-based virtual machine, the values are popped from & results are pushed into the stack. Its counterpart is a register-based machine where specific areas are assigned for the storage of values.
Why is this important to our discussion? WASM execution is defined in the form of stack machines. This means that when a WASM function is called, you start off with an empty stack that is gradually filled up & emptied as the instructions in the body section are executed.
What’s more? The WebAssembly validation rules ensure that if we define a return value of a particular type, say i32, the stack must exactly match the type at the end. If there is no return type, the stack must be empty.
So, let’s move on to writing our very first WASM program!
In the previous post, we looked at a program that returned us a number 42 (and also, doubled up as our Hello, World). Moving on to a slightly complex (but still pretty basic) program where we add two numbers and return a result.
(module (func (export "add_basic")(param $num1 i32) (param $num2 i32) (res i32) local.get $num1 local.get $num2 i32.add))
For a full list of valid instruction codes, you can check out the Semantic glossary for WebAssembly.
Now that we’ve learned how to write a basic .wat program, in the next part we shall look at memory management in WebAssembly. An often confusing aspect for developers that are well-versed in languages employing memory management techniques like garbage collection, we will explore various scenarios & try understanding the internals.
To stay updated with my latest tech shenanigans, do follow me on Twitter and LinkedIn. I also write a weekly newsletter, friday four, where I cover all the interesting goings-on in the world of tech as a highlight reel. Do consider subscribing if you feel like this is something up your alley :)