Compiling to and optimizing Wasm with Binaryen

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
 )
)