Binaryen optimizes the wasm your toolchain already produced
Optimizer and compiler/toolchain library for WebAssembly
At a glance
- What is it?
- Binaryen is the C++ optimizer and IR that Emscripten, wasm-pack, J2CL, Kotlin and Dart run as wasm-opt, and that AssemblyScript, wasm2js, Asterius and Grain link into their compilers. Its tree shaped IR buys simple passes and gives up a few things wasm text cannot express.
- Who is it for?
- Adopt Binaryen when your compiler needs a wasm shaped IR it can link against, or when you want a wasm-to-wasm optimizer you can bolt onto an existing build as a separate step. If you take it from source, plan for a C++ build: the repository ships CMakeLists.txt and src/.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly WebAssembly, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- WebAssembly/binaryen8,682 ★ · +50
| Repository | 2026-09-22 | 2026-11-14 |
|---|---|---|
| WebAssembly/binaryen | 8.6k | 8.7k |
50 Stars in the last 53 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
wasm-opt is the step your toolchain runs without telling you
Binaryen is a compiler and toolchain infrastructure library for WebAssembly, written in C++, and very few people who ship wasm meet it directly. Two groups use it. Toolchains take it as a component and run `wasm-opt`: Emscripten for C and C++, wasm-pack for Rust, J2CL for Java, Kotlin/Wasm, Dart through Flutter, and wasm_of_ocaml for OCaml. Compilers link it as a library: AssemblyScript compiles a variant of TypeScript with it, wasm2js turns WebAssembly into JavaScript from `src/wasm2js.h`, Asterius compiles Haskell, Grain compiles Grain.
The consequence for a reader is blunt. Optimization decisions you never made are being made by this project inside your build, and you discover them in a release note or a bundle diff. Four utilities sit on the library: parse and emit wasm, interpret wasm and run the spec tests, integrate with Emscripten, and polyfill wasm by running the interpreter compiled to JavaScript when a browser has no native support, which the README calls useful for testing.
A tree IR where wasm ships a stack machine
Binaryen's internal IR is a tree, hierarchical, because passes are easier to write against nested nodes. The WebAssembly binary format is a stack machine. Code that cannot be put into structured form goes to Stack IR, which Binaryen uses for exactly that case. What does not fit in wasm gets tuple types: multivalue instructions carry them, and locals and globals can hold them too, though the language has no such types. Block input values, currently only supported in `catch` blocks in the exception handling feature, show up as `pop` subexpressions.
The trade-off the project states about itself is the interesting part. Experiments showed that better multivalue support could yield useful but small code size savings of 1-3%, and that was not worth changing the core IR structure for. So code leaning on multivalue keeps paying a few percent of size for an IR every pass can share. Pass authors pay the other half of that bill: stack shaped code arrives as tuples and `pop` nodes they must reason about.
Only the block holds a list, and only named blocks can be branched to
One control flow structure in Binaryen IR contains a variable-length list of children: the block. Loops, if arms and function bodies have a single child, which may of course be a block. The stated reason is mechanical, since many passes need special code for iterating a list of instructions, so one IR node owning a list keeps that code simple. Branch targets resolve by name instead of nesting depth, and two things follow.
Blocks without names may not be branch targets. Names must be unique, though reading .wat files with duplicate names is supported, because the names get modified when the IR is constructed. Emission exploits the same property: a nameless block that can never be a branch target is not emitted at all, and its children are used directly inside the containing control flow structure. Generate IR by hand and you must mint a name before you can branch anywhere. Round trip a .wat file with duplicate labels and they come back renamed.
Default text output from Binaryen can be invalid wasm text
Binaryen IR has an unreachable type and allows block, if and loop to take it, which is what permits local transforms that do not need to know the global context. WebAssembly limits those types to none and the concrete value types i32, i64, f32 and f64.
The cost lands on the reader at the worst moment, because Binaryen's default text output is not necessarily valid wasm text. Anything that pipes that output into a wasm parser can fail on a block the optimizer created. The way out is the pair of flags named in the README, which print Stack IR, guaranteed to be valid for wasm parsers:
wasm-opt --generate-stack-ir --print-stack-ir`wasm-opt` is the driver the toolchains above run, and both flags appear together in the project's description of this fix. Read it as an inspection path rather than a shipping path. If your build compares text output between revisions, or hands it to a tool that parses wasm text, add these flags before you lose an afternoon to a parse error that is not your bug.
stringref is a subtype of externref, and that is not the proposal
Binaryen supports a `stringref` type, and here it parts company with the wasm proposal it resembles, which is currently inactive. The difference is the subtype: Binaryen's string type is a subtype of `externref` rather than `anyref`.
The point of the type is a round trip. Toolchains can emit code that uses js string builtins, Binaryen lifts that into `stringref` in its internal IR, optimizes it there, where a concatenation of "a" and "b" folds at compile time to "ab", then lowers it back into js string builtins. Folded constants survive into the output either way. What you cannot do is treat `stringref` as portable wasm. The type lives in Binaryen's IR and the proposal it tracks is not active, so a module leaning on it leans on this project's dialect instead of on a language guarantee.
Where to get Binaryen, and the one executable it names
The repository layout points to a CMake build: CMakeLists.txt, config.h.in, src/, third_party/, test/, fuzz/, check.py, .clang-format, .clang-tidy, .ruff.toml, ubsan.blacklist and requirements-dev.txt. An embedder in C++ builds that tree. The README links a C API described as living in a single header plus a JavaScript API.
The license is Apache-2.0 and it covers the library use and the utilities alike, so a toolchain shipping the optimizer ships that license with it. Contribution guidance sits in Contributing.md. Budget for a build before you budget for a feature.
Three tags in ten weeks means pin a tag, not track main
Binaryen is not archived, and the last push to the default branch was on 2026-11-01. The tags move just as fast: version_131 on 2026-07-15, version_132 on 2026-08-12, version_133 on 2026-09-21. That cadence is the maintenance fact that matters if you link the library, because the optimizer's passes and the IR itself both move, and the IR is the part you compile against.
A compiler that rebuilds against main picks up whatever the tree structure, the tuple typing and the `stringref` lowering did that month. The repository does carry test and fuzz directories plus check.py, which says the project tests itself, not that your embedder's build will. Pin the tag you validated against, and re-read the release notes before moving it.
Link the library or run wasm-opt, and pay for a different thing
Two ways in, with different bills. A toolchain that runs `wasm-opt` gets optimization as its own step over already emitted wasm, which the README describes as a wasm-to-wasm optimizer in a single command. Its passes see only what survived code generation, and the round trip costs a process boundary plus a parse and emit. A compiler that links the library builds the tree IR directly, can accept input in WebAssembly-like form or a general control flow graph, keeps the unreachable type for local transforms, and optimizes before anything is emitted.
That second path is the one that reaches the WebAssembly-specific optimizations a general-purpose compiler might not do, which the project compares to wasm minification for JavaScript and CSS. It also costs a C++ dependency and a rebuild whenever the IR changes. Toolchains that only want smaller output take the first path. Compiler authors with passes of their own need the second.
Editorial conclusion
Adopt Binaryen when your compiler needs a wasm shaped IR it can link against, or when you want a wasm-to-wasm optimizer you can bolt onto an existing build as a separate step. If you take it from source, plan for a C++ build: the repository ships CMakeLists.txt and src/. Before you commit, check whether your code depends on stringref, which Binaryen types as a subtype of externref while the wasm proposal it resembles is still inactive.
Frequently asked questions
How do you install Binaryen?
The repository ships CMakeLists.txt, config.h.in and src/, so a C++ embedder builds the tree from the repository, and the README links a C API in a single header and a JavaScript API as entry points. Toolchains typically run its wasm-opt tool.
Is Binaryen a replacement for LLVM when targeting WebAssembly?
The README states that Binaryen's IR is as close as possible to WebAssembly and that the optimizer concentrates on WebAssembly-specific optimizations general-purpose compilers might not do, which the project describes as wasm minification.
What changes if I link the Binaryen library instead of running wasm-opt?
Linking lets a compiler build the tree IR directly, take a general control flow graph as input, use the unreachable type for local transforms and optimize before emit. Running wasm-opt optimizes already emitted wasm in one command, and its passes only see what survived code generation.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/webassembly-binaryen)Star history chart for your README
[](https://hysenlabs.com/projects/webassembly-binaryen)Was this useful?