Skip to content

Binaryen and wasm-opt

Binaryen is Google’s optimising compiler infrastructure for WebAssembly. Its most widely used tool is wasm-opt, which takes an existing .wasm binary and rewrites it to produce a smaller or faster output. You do not need to understand Binaryen’s internal IR to benefit from it — a single command can cut 20–40 % off a release binary.

wasm-opt applies a sequence of compiler passes: dead-code elimination, constant folding, function inlining, local CSE, and many more. It works entirely on the binary level — it does not need the original source or any metadata.

Terminal window
# Install globally via npm
npm install -g binaryen
# Or install via Homebrew on macOS
brew install binaryen

wasm-opt uses the same level flags as Clang/GCC:

Terminal window
# -O1 — light optimisation (fast build)
wasm-opt -O1 input.wasm -o output.wasm
# -O2 — balanced (same as release builds in most toolchains)
wasm-opt -O2 input.wasm -o output.wasm
# -O3 — aggressive (maximise speed, larger code possible)
wasm-opt -O3 input.wasm -o output.wasm
# -Os — optimise for size (speed is secondary)
wasm-opt -Os input.wasm -o output.wasm
# -Oz — maximise size reduction (slowest optimisation)
wasm-opt -Oz input.wasm -o output.wasm

For most production deployments, -Oz offers the best trade-off: smaller download, faster parse, warm cache.

In practice you rarely call wasm-opt directly. These popular toolchains invoke it automatically in release mode:

ToolchainWhen it runs wasm-opt
Emscripten-O2 / -O3 release flags
wasm-pack (Rust)wasm-pack build --release
AssemblyScriptasc --optimize
Go (TinyGo)-opt=z flag

If you are consuming a .wasm from any of these, the binary is probably already optimised. Running wasm-opt a second time is safe but yields diminishing returns.

Terminal window
# Strip the DWARF debug section (reduces size, loses stack traces)
wasm-opt -Oz --strip-dwarf input.wasm -o output.wasm
# Strip all custom sections
wasm-opt -Oz --strip-producers input.wasm -o output.wasm
# Print module statistics before and after
wasm-opt -Oz --print-stats input.wasm -o output.wasm
# Keep a name section for readable stack traces
wasm-opt -Oz --debuginfo input.wasm -o output.wasm

Always measure before and after. A quick bash one-liner shows the size difference:

Terminal window
wasm-opt -Oz input.wasm -o output.wasm
echo "Before: $(wc -c < input.wasm) bytes"
echo "After: $(wc -c < output.wasm) bytes"
OptionBenefitCost
wasm-opt -Oz (most aggressive optimization)Best combination of size and speedLonger build times, and the mapping between source and output drifts further apart, making debugging harder
Keeping --debug-namesReadable stack traces, much easier debuggingIncreases the size of the binary you ship to users
  • Skipping wasm-opt entirely and shipping the .wasm straight out of the compiler, missing size/speed gains that are nearly free
  • Reaching for -O3 or -Oz at random without measuring before and after, so you never learn which flag actually helped or by how much
  • Stripping --debug-names from dev builds too, so when a real crash needs debugging there’s no readable stack trace left

💡 Real-world example

Emscripten automatically invokes Binaryen’s wasm-opt when you build with -O2/-O3 (Emscripten optimization docs) — you don’t have to call it yourself in that pipeline.

Which wasm-opt flag maximises binary size reduction?
Do you need the original source code to run wasm-opt?
Which toolchain calls wasm-opt automatically during a release build?
What does --strip-dwarf do?