Binaryen is a compiler and toolchain
infrastructure library for WebAssembly, written in C++. It aims to make
compiling to WebAssembly intuitive, fast, and effective. In this post, using the
example of a synthetic toy language called ExampleScript, learn how to write
WebAssembly modules in JavaScript using the Binaryen.js API. You'll cover the
basics of module creation, function addition to the module, and exporting
functions from the module. This will give you knowledge about the overall
mechanics of compiling actual programming languages to WebAssembly. Further,
you'll learn how to optimize Wasm modules both with Binaryen.js and on the
command line with wasm-opt.
Background on Binaryen
Binaryen has an intuitive C API in a single header, and can also be used from JavaScript. It accepts input in WebAssembly form, but also accepts a general control flow graph for compilers that prefer that.
An intermediate representation (IR) is the data structure or code used internally by a compiler or virtual machine to represent source code. Binaryen's internal IR uses compact data structures and is designed for completely parallel code generation and optimization, using all available CPU cores. Binaryen's IR compiles down to WebAssembly due to being a subset of WebAssembly.
Binaryen's optimizer has many passes that can improve code size and speed. These optimizations aim to make Binaryen powerful enough to be used as a compiler backend by itself. It includes WebAssembly-specific optimizations (that general-purpose compilers might not do), which you can think of as Wasm minification.
AssemblyScript as an example user of Binaryen
Binaryen is used by a number of projects, for example, AssemblyScript, which uses Binaryen to compile from a TypeScript-like language directly to WebAssembly. Try the example in the AssemblyScript playground.
AssemblyScript input:
export function add(a: i32, b: i32): i32 {
return a + b;
}
Corresponding WebAssembly code in textual form generated by Binaryen:
(module
(type $0 (func (param i32 i32) (result i32)))
(memory $0 0)
(export "add" (func $module/add))
(export "memory" (memory $0))
(func $module/add (param $0 i32) (param $1 i32) (result i32)
local.get $0
local.get $1
i32.add
)
)