This post is still a work in progress and will be updated.
This document is my notes from my experiments with using WebAssembly. Use at your own risk!
You might want to check out these examples in a real repo, with a Makefile: github.com/richinfante/wasm-examples.
There’s a couple requirements to get this to work. First, we need a webserver which serves
application/wasm content-types for your compiled webassembly modules. Browsers won’t load them otherwise. You can generate
.wasm binaries using the WABT Toolkit, specifically the
To compile, you simply run something like the following:
wat2wasm <file> -o <outfile>
Another useful tool is this Reference Guide which list the available instructions and opcodes.
Basic Module Declaration
This module contains one function, which just returns a constant 32-bit integer
1337. In wasm modues, the only I/O types allowed for functions are numbers. We can get around this using memory and indices/lengths as you’ll see later.
We save this in a file (I called it example1/main.wasm). Then, I ran
wat2wasm example1/main.wast -o example1/main.wasm to compile it.
(module (func (export "hello_world") (result i32) ;; Return a constant number (i32.const 1337) ) )
Next, I serve an web page containing the following script. For this, I’m using my simple server from Here.
As you saw above, that function just prints 1337. To accomplish this can use this instruction which is a rough equivalent of loading an immediate in normal assembly:
i32.const 1337 ;; This just pushes the constant 1337 onto the stack.
Wasm functions as a basic stack machine. Operations like
get_local push onto the stack, and operations like
i32.mul both pull two elements and push a single one. This module just adds two numbers together:
(module (func $add_i32 (export "add_i32") (param $p1 i32) (param $p2 i32) (result i32) ;; All Wasm items are accessed via their index. ;; $p1 and $p2 are just aliases for the numbers 0 and 1, respectively. ;; Therefore, we could substitute them below and it'd work fine, but this is clearer: get_local $p1 get_local $p2 i32.add ) )
If we add this function to the above module, we can see how we can use our own functions in the same manner. Each call to
$add_i32 pops two i32 from the stack and pushes one. You’ll notice the
$add_i32 in the declaration of the function in the previous step, which is a labeling scheme just like the function params above. This follows the same numbering conventions as the local variables. You could do
(call 0) in the function below, but that’s a bad practice because adding one new function will make all your references wrong.
;; Function to add three numbers. (func (export "add_i32x3") (param $p1 i32) (param $p2 i32) (param $p3 i32) (result i32) ;; Load params onto stack get_local $p1 get_local $p2 ;; Call add func call $add_i32 ;; Load third param get_local $p3 ;; Call add again. call $add_i32 )
(module ;; Console.log via js environment (import "console" "log" (func $log (param i32 i32))) ;; Memory semgent (imported) (import "js" "mem" (memory 1)) ;; Constant data at offset 0: (data (i32.const 0) "Hello, world!") ;; Some function (func (export "main") ;; "Hello, world!" string has offset 0 and length 13 in shared memory: (call $log (i32.const 0) (i32.const 13)) ) )
(module ;; Global mutable counter (global $counter (mut i32) (i32.const 0)) ;; Increase the counter (func (export "inc_counter") (result i32) ;; Load + Add i32.const 1 get_global $counter i32.add ;; Save Result set_global $counter ;; Return result get_global $counter ) ;; Get current counter value (func (export "get_counter") (result i32) get_global $counter ) )
To be continued!...
2020-01-03- Initial Revision
Found a typo or technical problem? file an issue!