wabt is a WebAssembly binary toolkit that provides compilation, analysis, debugging, and validation tools for wasm-related code. This article briefly introduces the usage of common commands.
Compiling wat Code
Implementing the Fibonacci sequence in wat:
;; fib.wat
(module
(import "env" "log" (func $log (param i32)))
;; Allocate one page of memory
(memory (export "memory") 1)
;; Global variable: heap pointer (points to next available memory address)
(global $heap_ptr (mut i32) (i32.const 0))
;; Allocate memory block
;; params: size (i32) - bytes to allocate
;; return: starting address (i32)
(func $allocate (param $size i32) (result i32)
(local $start i32)
(local.set $start (global.get $heap_ptr))
(global.set $heap_ptr
(i32.add
(global.get $heap_ptr)
(local.get $size)
)
)
(local.get $start)
)
;; Fibonacci sequence
;; params: n (i32) - array length
;; return: array starting address (i32)
(func (export "fib") (param $n i32) (result i32)
(local $i i32)
(local $arr_ptr i32)
(local $prev i32)
(local $curr i32)
(local $next i32)
;; Allocate memory: n * sizeof(i32) = n * 4
(local.set $arr_ptr
(call $allocate
(i32.mul
(local.get $n)
(i32.const 4)
)
)
)
;; Handle edge cases
(if (i32.le_s (local.get $n) (i32.const 0))
(then (return (local.get $arr_ptr))) ;; Return empty array address
)
;; Initialize first two elements
(i32.store (local.get $arr_ptr) (i32.const 0))
(if (i32.gt_s (local.get $n) (i32.const 1))
(then
(i32.store
(i32.add (local.get $arr_ptr) (i32.const 4))
(i32.const 1)
)
)
)
;; Iteratively calculate subsequent elements
(local.set $prev (i32.const 0))
(local.set $curr (i32.const 1))
(local.set $i (i32.const 2))
(loop $loop
;; Calculate next Fibonacci number
(local.set $next (i32.add (local.get $prev) (local.get $curr)))
(local.set $prev (local.get $curr))
(local.set $curr (local.get $next))
;; Store in memory
(i32.store
(i32.add
(local.get $arr_ptr)
(i32.mul (local.get $i) (i32.const 4))
)
(local.get $next)
)
;; Loop control
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br_if $loop (i32.lt_s (local.get $i) (local.get $n)))
)
;; Return array starting address
(local.get $arr_ptr)
)
)
Compile the code using wabt:
wat2wasm ./fib.wat -o ./fib.wasm
Resulting wasm files:
├── fib.wasm
├── fib.wat
└── main.ts
main.ts provides the host environment for calling wasm code:
import fibUrl from './fib.wasm?url';
WebAssembly.instantiateStreaming(fetch(fibUrl), {
env: {
log: (value: string | number) => console.log(value)
}
}).then(res => {
// Destructure exports to get fib function and memory
const { fib, memory } = res.instance.exports as unknown as {
fib: (n: number) => number;
memory: WebAssembly.Memory
};
// Fibonacci sequence length
const n = 10;
// Compute, storing result in memory, get result pointer
const addr = fib(n);
// Read result
const buffer = new Uint32Array(memory.buffer, addr, n);
const result = Array.from(buffer);
console.log(result);
})
Using Vite as the build tool, which supports importing any resource type as a URL. Here we convert wasm to a resource URL and load it using WebAssembly.instantiateStreaming
and fetch
. Note that Vite also supports automatic wasm initialization using the ?init
suffix (see Vite documentation for details).
We use the fib
function to compute a Fibonacci sequence of length 10, with output:
Results match expectations.
Comparing wat code and wasm file sizes:
After compilation, the wasm file is an order of magnitude smaller than the source. This efficiency comes from both wasm's compact format and LEB128 compression.
Analyzing wasm
The wasm-objdump
command is similar to the OS objdump
, used for analyzing wasm file information. Practical use case: A developer receives a wasm module and wants to quickly identify exported functions, their parameter counts/types, and return value types/lengths. Using the previous fib.wasm as an example:
wasm-objdump ./fib.wasm -j Export -x
Output:
fib.wasm: file format wasm 0x1
Section Details:
Export[2]:
- memory[0] -> "memory"
- func[2] <fib> -> "fib"
This shows fib.wasm has two exports: a memory and a fib
function (type 2). Next, examine function signatures:
wasm-objdump ./fib.wasm -j Function -x
Output:
fib.wasm: file format wasm 0x1
Section Details:
Function[2]:
- func[1] sig=1
- func[2] sig=1 <fib>
Function 2 uses type index 1 declared in the Type
section. Export the Type
section:
wasm-objdump ./fib.wasm -j Type -x
Type information:
fib.wasm: file format wasm 0x1
Section Details:
Type[2]:
- type[0] (i32) -> nil
- type[1] (i32) -> i32
We've now obtained complete information about fib.wasm's exports:
- Exported memory
- Exported function
fib
with onei32
parameter and onei32
return value
wasm-objdump -x
can be used alone to output all section information. For large files, it's better to output sections individually for easier analysis.
Formatting Code
The wat-desugar
command formats existing wat code to conform to certain specifications. For example, the original fib.wat source didn't strictly follow the "push operands -> execute instruction" pattern, often writing operands after instructions. While valid, this doesn't follow stack machine conventions. wat-desugar
helps standardize this code. Here's the formatted $allocate
function:
(func $allocate (param $size i32) (result i32)
(local $start i32)
global.get $heap_ptr
local.set $start
global.get $heap_ptr
local.get $size
i32.add
global.set $heap_ptr
local.get $start
)
Compared to the original source, the formatted code is more compact and better follows stack-based calling conventions, though less readable. The most noticeable difference is the i32.add
operation: the original placed operands after the instruction, while the standardized version pushes operands to the stack before calling the add instruction.
The name "desugar" contrasts with "Syntactic Sugar". Syntax like
(i32.add (local.get 0) (local.get 1))
is syntactic sugar - operands aren't pushed to the stack before the instruction call (more like a register machine). Compilers accept this non-stack-machine syntax because it's more intuitive. "Desugar" is like the Cantonese phrase "走糖" (less sugar), removing the syntactic sugar to reveal the most fundamental code that strictly follows the push-operands-then-execute pattern.
Disassembly
wabt provides three disassembly commands:
wasm2wat
: Disassembles wasm to wat codewasm2c
: Disassembles wasm to C source and header fileswasm-decompile
: Disassembles wasm to readable C-style pseudocode
In practice, wasm-decompile
combined with wasm2wat
is most useful. Use wasm-decompile
to analyze functionality implementation. If minor module modifications are needed, use wasm2wat
to get a wat file, make changes, then recompile to wasm.
Top comments (0)