WASM By Hand - WAT Syntax (WebAssembly Text Format) - Part 02
Let's learn the basic WAT syntax
đ You are here: Chapter 02 (Theory)
âąď¸ Time to Complete: 25-30 minutes
đ Prerequisites: Completed Chapter 01 (Introduction)
đŻ Learning Objectives:
Understand WAT module structure
Learn function syntax and parameter handling
Master stack-based operations with visual examples
Understand control flow (blocks, loops, if-else)
Learn memory operations (load/store)
Grasp import/export mechanisms
What is WAT?
WAT (WebAssembly Text Format) is the human-readable text representation of WebAssembly.
The compilation flow:
1. Write: example.wat (human-readable text)
2. Compile: wat2wasm example.wat -o example.wasm
3. Run: example.wasm (binary format)
Think of it like:
WAT = Assembly language (human-readable)
WASM = Machine code (binary)
wat2wasm = Assembler (converts text to binary)
Module Structure
Every WAT file is a module:
(module
;; Everything lives inside here
;; Semicolons start comments
)A module can contain:
Functions
Memory
Imports
Exports
Global variables
Tables (for function references)
Functions
Basic Function Syntax
(func $name
(param i32 i32)
(result i32)
;; instructions here
)Components:
$name- Optional function name (starts with$)(param type type ...)- Parameter types(result type)- Return type (optional, can be omitted for void)Instructions - Function body
Parameter Access
Parameters are accessed by index (0-based):
(func $add (param i32 i32) (result i32)
local.get 0 ;; Get first parameter
local.get 1 ;; Get second parameter
i32.add ;; Add them
)Stack visualization:
Called with: add(5, 3)
Step 1: local.get 0
Stack: [5]
Step 2: local.get 1
Stack: [5, 3]
Step 3: i32.add
Stack: [8] â Result
Named parameters (more readable):
(func $add (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.add
)đĄ Best Practice: Always use named parameters for clarity!
Locals
Functions can have local variables (in addition to parameters):
(func $example
(local i32) ;; Anonymous local at index 0
(local $sum i32) ;; Named local
i32.const 42
local.set $sum ;; Set local variable
local.get $sum ;; Get local variable
)Important:
Locals are zero-initialized
Use
local.getto readUse
local.setto write
Control Flow (Structured)
WebAssembly uses structured control flow (no goto):
Block
(block $label
;; code
br $label ;; Break out of block
)Loop
(loop $label
;; code
br $label ;; Continue loop (jump to start)
)If-Else
(if (result i32)
(i32.const 1) ;; Condition (non-zero = true)
(then
i32.const 10
)
(else
i32.const 20
)
)Branch Instructions
br $label- Unconditional branchbr_if $label- Branch if top of stack is non-zero
Linear Memory
WebAssembly has a flat byte array called linear memory:
(memory 1) ;; Allocate 1 page (64KB)Memory characteristics:
Grows in 64KB pages
Byte-addressable (like C arrays)
Shared between Wasm and host (JavaScript)
Memory Operations
Store (write):
i32.const 0 ;; Address
i32.const 42 ;; Value
i32.store ;; Store value at addressLoad (read):
i32.const 0 ;; Address
i32.load ;; Load value from addressDifferent sizes:
i32.load8_u- Load 1 byte (unsigned)i32.load16_u- Load 2 bytes (unsigned)i32.store8- Store 1 bytei32.store16- Store 2 bytes
Imports & Exports
Exports
Make functions/memory available to the host:
(func $add (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add
)
(export âaddâ (func $add))Shorthand:
(func (export âaddâ) (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add
)Imports
Use functions provided by the host:
(import âenvâ âlogâ (func $log (param i32)))
(func (export ârunâ)
i32.const 777
call $log ;; Call imported function
)Import format: (import "namespace" "name" (func ...))
Common Instructions
Arithmetic
i32.add,i32.sub,i32.mul,i32.div_s(signed),i32.div_u(unsigned)f32.add,f32.sub,f32.mul,f32.div
Comparison
i32.eq,i32.ne,i32.lt_s,i32.gt_s,i32.le_s,i32.ge_sSuffix
_s= signed,_u= unsigned
Logical
i32.and,i32.or,i32.xor
Constants
i32.const 42- Push constant to stackf32.const 3.14
Common Beginner Mistakes
â Mistake #1: Forgetting to Export Functions
Wrong:
(module
(func $add (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add
)
;; Missing export! JavaScript canât call this function
)Correct:
(module
(func $add (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add
)
(export âaddâ (func $add)) ;; â
Now JavaScript can call it
)â Mistake #2: Type Mismatches
Wrong:
i32.const 5
f32.add ;; ERROR: Expected f32 on stack, got i32Correct:
f32.const 5.0
f32.const 3.0
f32.add ;; â
Both operands are f32â Mistake #3: Stack Underflow
Wrong:
i32.add ;; ERROR: Need 2 values on stack, but stack is emptyCorrect:
i32.const 5
i32.const 3
i32.add ;; â
Stack has 2 valuesâ Mistake #4: Wrong Memory Access
Wrong:
(module
;; No memory declared!
(func (export âtestâ)
i32.const 0
i32.load ;; ERROR: No memory to load from
)
)Correct:
(module
(memory 1) ;; â
Declare memory first
(func (export âtestâ)
i32.const 0
i32.load
)
)â Mistake #5: Forgetting Return Values
Wrong:
(func $add (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add
drop ;; ERROR: Function promises i32 result, but stack is empty!
)Correct:
(func $add (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add
;; â
Result stays on stack and is returned
)Memory Layout Visualization
Understanding how data is laid out in linear memory:
Address: 0 1 2 3 4 5 6 7 8 9 10 11
+----+----+----+----+----+----+----+----+----+----+----+----+
Bytes: | 2A | 00 | 00 | 00 | FF | 00 | 00 | 00 | 48 | 65 | 6C | 6C |
+----+----+----+----+----+----+----+----+----+----+----+----+
|<-- i32 (42) -->| |<-- i32 (255) ->| |<-- "Hell" -->|
Explanation:
- Address 0-3: i32 value 42 (little-endian: 0x0000002A)
- Address 4-7: i32 value 255 (little-endian: 0x000000FF)
- Address 8-11: String "Hell" (ASCII bytes)
Little-endian: Least significant byte comes first
Value: 0x12345678
Memory: [78, 56, 34, 12]
^^ ^^ ^^ ^^
LSB MSBPractice Exercises
Try writing these functions yourself:
Exercise 1: Multiply Function
Write a function that multiplies two i32 numbers.
Solution
(module
(func (export âmultiplyâ) (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.mul
)
)Exercise 2: Max Function
Write a function that returns the larger of two i32 numbers.
HintUse `i32.gt_s` (greater than, signed) and `if` expression.Solution
(module
(func (export âmaxâ) (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.gt_s
(if (result i32)
(then
local.get $a
)
(else
local.get $b
)
)
)
)Exercise 3: Store and Load
Write a function that stores a value at address 0 and then loads it back.
Solution
(module
(memory 1)
(func (export âstoreAndLoadâ) (param $value i32) (result i32)
;; Store value at address 0
i32.const 0
local.get $value
i32.store
;; Load value from address 0
i32.const 0
i32.load
)
)WAT Syntax Cheat Sheet
Function Declaration
(func $name (param $p1 type) (result type)
;; body
)Stack Operations
Arithmetic (i32)
Comparison (i32)
Control Flow
(block $label
;; code
br $label ;; break
)
(loop $label
;; code
br $label ;; continue
)
(if (result type)
(condition)
(then
;; if true
)
(else
;; if false
)
)Memory
(memory 1) ;; Declare 1 page (64KB)
i32.load ;; Load 4 bytes
i32.load8_u ;; Load 1 byte (unsigned)
i32.store ;; Store 4 bytes
i32.store8 ;; Store 1 byteLearning Checkpoint
Test your understanding:
What does this code do?
i32.const 10
i32.const 5
i32.subAnswerPushes 10, pushes 5, subtracts (10 - 5 = 5). Result: 5 on stack.
Whatâs wrong with this code?
(func $test (result i32)
i32.const 42
drop
)AnswerFunction declares it returns i32, but `drop` removes the value from stack. Stack is empty at function end, causing an error.
How do you access the second parameter?
(func $test (param $a i32) (param $b i32))Answer`local.get $b` or `local.get 1` (0-indexed)
Whatâs the difference between
brin a block vs a loop?AnswerIn a block, `br` breaks out (exits). In a loop, `br` continues (jumps to start).
How many bytes does
i32.storewrite to memory?Answer4 bytes (32 bits = 4 bytes)
â
Chapter Summary
Key Takeaways:
WAT is human-readable - Compiles to binary
.wasmwithwat2wasmEverything is in a module - Functions, memory, imports, exports
Functions use stack-based execution - No registers, only stack operations
Structured control flow -
block,loop,if(nogoto)Linear memory is a flat byte array - Explicit load/store operations
No built-in strings - Must use memory + (pointer, length) pattern
Always export functions - Otherwise JavaScript canât call them
Type safety is strict - Stack must have correct types for each instruction
đ Next Steps
You now understand WAT syntax! Next, letâs set up your development environment.
Continue to: Part 3: Setup
đ Additional Resources
Previous: Part 1: Introduction
Previous: Part 1: Introduction
Next: Part 3: Setup




