Skip to content

Your First Wasm Module

Every Wasm journey starts with the same loop: write a module in the WebAssembly Text format (WAT), compile it to bytes, instantiate it, and call an exported function. This lesson walks through that loop end-to-end with the smallest possible example — a double function that multiplies its argument by two.

By the end you will have written real WAT, compiled it live in the browser, and called it from JavaScript.

WAT is the human-readable text representation of a .wasm binary. Here is the complete module:

(module
(func (export "double") (param $n i32) (result i32)
local.get $n
i32.const 2
i32.mul))

Let’s break down every line:

  • (module ...) — the top-level wrapper. Every .wasm file contains exactly one module.
  • (func (export "double") ...) — declares a function and immediately makes it visible to the host as "double". The export name is what JavaScript sees on instance.exports.
  • (param $n i32) — declares one parameter named $n of type i32 (a 32-bit signed integer). The $n label is a WAT convenience — at the binary level it is just an index.
  • (result i32) — declares that this function returns one i32.
  • local.get $n — pushes the current value of $n onto the operand stack.
  • i32.const 2 — pushes the constant 2 onto the stack.
  • i32.mul — pops the top two stack values, multiplies them, and pushes the result. Because this is the last instruction and its type matches (result i32), the result becomes the return value.

Wasm’s stack-based design means there is no explicit return — the value left on top of the stack at the end of the function body is the return value.

The sandbox exposes a global compileWat(watString) function powered by the WABT library. It takes your WAT source string and returns a Promise<Uint8Array> — the raw .wasm bytes you would normally get from a file on disk or a network fetch.

const bytes = await compileWat(wat);

WebAssembly.instantiate(bytes, importObject) is the standard browser API for creating a runnable Wasm instance. It returns a Promise that resolves to a { instance, module } pair:

  • instance — the live, runnable object. Its .exports property contains all the functions and memories the module exported.
  • module — the compiled but inert WebAssembly.Module. You can share and re-instantiate it without recompiling.

The second argument is the import object — a nested object of functions, memories, and globals you provide to the module from JavaScript. Because our double function imports nothing from the host, we pass {}.

const { instance } = await WebAssembly.instantiate(bytes, {});

Once instantiated, every exported function is available as a plain JavaScript function on instance.exports:

instance.exports.double(21); // → 42

JavaScript numbers are automatically coerced to/from i32. The call is synchronous — no await needed.

The runner below compiles the WAT, instantiates it, and calls double with three different inputs. Run it and watch the results in the console.

WebAssembly
What does `WebAssembly.instantiate(bytes, {})` return?
What does the empty object `{}` represent in `WebAssembly.instantiate(bytes, {})`?
In the `double` WAT function, which instruction produces the final return value?