| <div align="center"> |
| <a href="https://github.com/WebAssembly/binaryen"> |
| <img src="media/logo.svg" width=300 alt="Binaryen logo"/> |
| </a> |
| <br> |
| </div> |
| |
| [](https://github.com/WebAssembly/binaryen/actions/workflows/ci.yml?branch=main&event=push) |
| |
| # Binaryen |
| |
| Binaryen is a compiler and toolchain infrastructure library for WebAssembly, |
| written in C++. It aims to make [compiling to WebAssembly] **easy, fast, and |
| effective**: |
| |
| * **Easy**: Binaryen has a simple [C API] in a single header, and can also be |
| [used from JavaScript][JS_API]. It accepts input in [WebAssembly-like |
| form][compile_to_wasm] but also accepts a general [control flow graph] for |
| compilers that prefer that. |
| |
| * **Fast**: Binaryen's internal IR uses compact data structures and is designed |
| for completely parallel codegen and optimization, using all available CPU |
| cores. Binaryen's IR also compiles down to WebAssembly extremely easily and |
| quickly because it is essentially a subset of WebAssembly. |
| |
| * **Effective**: Binaryen's optimizer has many passes (see an overview later |
| down) that can improve code size and speed. These optimizations aim to make |
| Binaryen powerful enough to be used as a [compiler backend][backend] by |
| itself. One specific area of focus is on WebAssembly-specific optimizations |
| (that general-purpose compilers might not do), which you can think of as |
| wasm [minification], similar to minification for JavaScript, CSS, etc., all |
| of which are language-specific. |
| |
| Toolchains using Binaryen as a **component** (typically running `wasm-opt`) include: |
| |
| * [`Emscripten`](http://emscripten.org) (C/C++) |
| * [`wasm-pack`](https://github.com/rustwasm/wasm-pack) (Rust) |
| * [`J2CL`](https://j2cl.io/) (Java; [`J2Wasm`](https://github.com/google/j2cl/tree/master/samples/wasm)) |
| * [`Kotlin`](https://kotl.in/wasmgc) (Kotlin/Wasm) |
| * [`Dart`](https://flutter.dev/wasm) (Flutter) |
| * [`wasm_of_ocaml`](https://github.com/ocaml-wasm/wasm_of_ocaml) (OCaml) |
| |
| For more on how some of those work, see the toolchain architecture parts of |
| the [V8 WasmGC porting blogpost](https://v8.dev/blog/wasm-gc-porting). |
| |
| Compilers using Binaryen as a **library** include: |
| |
| * [`AssemblyScript`](https://github.com/AssemblyScript/assemblyscript) which compiles a variant of TypeScript to WebAssembly |
| * [`wasm2js`](https://github.com/WebAssembly/binaryen/blob/main/src/wasm2js.h) which compiles WebAssembly to JS |
| * [`Asterius`](https://github.com/tweag/asterius) which compiles Haskell to WebAssembly |
| * [`Grain`](https://github.com/grain-lang/grain) which compiles Grain to WebAssembly |
| |
| Binaryen also provides a set of **toolchain utilities** that can |
| |
| * **Parse** and **emit** WebAssembly. In particular this lets you load |
| WebAssembly, optimize it using Binaryen, and re-emit it, thus implementing a |
| wasm-to-wasm optimizer in a single command. |
| * **Interpret** WebAssembly as well as run the WebAssembly spec tests. |
| * Integrate with **[Emscripten](http://emscripten.org)** in order to provide a |
| complete compiler toolchain from C and C++ to WebAssembly. |
| * **Polyfill** WebAssembly by running it in the interpreter compiled to |
| JavaScript, if the browser does not yet have native support (useful for |
| testing). |
| |
| Consult the [contributing instructions](Contributing.md) if you're interested in |
| participating. |
| |
| ## Binaryen IR |
| |
| Binaryen's internal IR is designed to be |
| |
| * **Flexible and fast** for optimization. |
| * **As close as possible to WebAssembly** so it is simple and fast to convert |
| it to and from WebAssembly. |
| |
| There are a few differences between Binaryen IR and the WebAssembly language: |
| |
| * Tree structure |
| * Binaryen IR [is a tree][binaryen_ir], i.e., it has hierarchical structure, |
| for convenience of optimization. This differs from the WebAssembly binary |
| format which is a stack machine. |
| |
| * Binaryen uses Stack IR to optimize "stacky" code (that can't be |
| represented in structured form). |
| |
| * When stacky code must be represented in Binaryen IR, such as with |
| multivalue instructions and blocks, it is represented with tuple types that |
| do not exist in the WebAssembly language. In addition to multivalue |
| instructions, locals and globals can also have tuple types in Binaryen IR |
| but not in WebAssembly. Experiments show that better support for |
| multivalue could enable useful but small code size savings of 1-3%, so it |
| has not been worth changing the core IR structure to support it better. |
| |
| * Block input values (currently only supported in `catch` blocks in the |
| exception handling feature) are represented as `pop` subexpressions. |
| |
| * Types and unreachable code |
| |
| * WebAssembly limits block/if/loop types to none and the concrete value types |
| (i32, i64, f32, f64). Binaryen IR has an unreachable type, and it allows |
| block/if/loop to take it, allowing [local transforms that don't need to |
| know the global context][unreachable]. As a result, Binaryen's default |
| text output is not necessarily valid wasm text. (To get valid wasm text, |
| you can do `--generate-stack-ir --print-stack-ir`, which prints Stack IR, |
| this is guaranteed to be valid for wasm parsers.) |
| |
| * Binaryen supports a `stringref` type. This is similar to the currently- |
| inactive [stringref proposal], with the difference that the string type is a |
| subtype of `externref` rather than `anyref`. Doing so allows toolchains to |
| emit code in a form that uses [js string builtins] which Binaryen can then |
| "lift" into stringref in its internal IR, optimize (for example, a |
| concatenation of "a" and "b" can be optimized at compile time to "ab"), and |
| then "lower" that into js string builtins once more. |
| |
| * Blocks |
| |
| * Binaryen IR has only one control flow structure that contains a |
| variable-length list of children: the block. WebAssembly on the other hand |
| allows all control flow structures, such as loops, if arms, and function |
| bodies, to have multiple children. In Binaryen IR, these other control flow |
| structures have a single child. This child may of course be a block. The |
| motivation for this property is that many passes need special code for |
| iterating on lists of instructions, so having a single IR node with a list |
| simplifies them. |
| |
| * As in the Wasm text format, blocks and loops may have names. Branch targets |
| in the IR are resolved by name (as opposed to nesting depth). This has 2 |
| consequences: |
| |
| * Blocks without names may not be branch targets. |
| |
| * Names are required to be unique. (Reading .wat files with duplicate names |
| is supported; the names are modified when the IR is constructed). |
| |
| * As an optimization, a block with no name, which can never be a branch |
| target, will not be emitted when generating wasm. Instead its list of |
| children will be directly used in the containing control flow structure. |
| Such a block is sometimes called an "implicit block". |
| |
| * Reference Types |
| |
| * The wasm text and binary formats require that a function whose address is |
| taken by `ref.func` must be either in the table, or declared via an |
| `(elem declare func $..)`. Binaryen will emit that data when necessary, but |
| it does not represent it in IR. That is, IR can be worked on without needing |
| to think about declaring function references. |
| |
| * Binaryen IR allows non-nullable locals in the form that the Wasm spec does, |
| in which a `local.get` must be structurally dominated by a `local.set` in |
| order to validate (that ensures we do not read the default value of null). |
| Despite being aligned with the Wasm spec, there are some minor details that |
| you may notice: |
| |
| * A nameless `Block` in Binaryen IR does not interfere with validation. |
| Nameless blocks are never emitted into the binary format (we just emit |
| their contents), so we ignore them for purposes of validating |
| non-nullable locals. As a result, if you read wasm text emitted by |
| Binaryen then you may see what seems to be code that should not validate |
| per the spec (and may not validate in Wasm text parsers), but that |
| difference will not exist in the binary format (binaries emitted by |
| Binaryen will always work everywhere, aside from bugs of course). |
| |
| * The Binaryen pass runner will automatically fix up validation after each |
| pass (finding things that do not validate and fixing them up, usually by |
| demoting a local to be nullable). As a result you do not need to worry |
| much about this when writing Binaryen passes. For more details see the |
| `requiresNonNullableLocalFixups()` hook in `pass.h` and the |
| `LocalStructuralDominance` class. |
| |
| * Binaryen IR uses the most refined types possible for references, |
| specifically: |
| |
| * The IR type of a `ref.func` is always an exact, non-nullable reference to |
| a defined function type, and not plain `funcref`, even if no features |
| beyond basic reference types are enabled. |
| |
| * The IR type of allocation instructions such as `struct.new` or |
| `array.new` is always an exact reference, even if Custom Descriptors are |
| not enabled. |
| |
| * Non-nullable types are also used for the type that `try_table` sends |
| on branches (if we branch, a null is never sent), that is, it sends |
| (ref exn) and not (ref null exn). |
| |
| * As a result, non-nullable and exact references are generally allowed in |
| the IR even when GC or Custom Descriptors is not enabled. When reading a |
| binary, the more refined types will be applied as we build the IR. |
| |
| In all cases the binary writer will generalize the type as necessary for |
| the enabled feature set. For example, if only Reference Types is enabled, |
| all function reference types will be emitted as `funcref`. |
| |
| * `br_if` output types are more refined in Binaryen IR: they have the type of |
| the sent value operand, when it exists. In the Wasm spec the type is that |
| of the branch target, which may be less refined. Using the more refined |
| type here ensures that we optimize in the best way possible, using all the |
| type information, but it does mean that some roundtripping operations may |
| look a little different. In particular, when we emit a `br_if` whose type |
| is more refined in Binaryen IR, then we emit a cast right after it to |
| recover the more refined type. That may cause a few bytes of extra size in |
| rare cases (we avoid this overhead in the common case where the `br_if` |
| value is unused). |
| |
| As a result, you might notice that round-trip conversions (wasm => Binaryen IR |
| => wasm) change code a little in some corner cases. |
| |
| * When optimizing Binaryen uses an additional IR, Stack IR (see |
| `src/wasm-stack.h`). Stack IR allows a bunch of optimizations that are |
| tailored for the stack machine form of WebAssembly's binary format (but Stack |
| IR is less efficient for general optimizations than the main Binaryen IR). If |
| you have a wasm file that has been particularly well-optimized, a simple |
| round-trip conversion (just read and write, without optimization) may cause |
| more noticeable differences, as Binaryen fits it into Binaryen IR's more |
| structured format. If you also optimize during the round-trip conversion then |
| Stack IR opts will be run and the final wasm will be better optimized. |
| |
| Notes when working with Binaryen IR: |
| |
| * As mentioned above, Binaryen IR has a tree structure. As a result, each |
| expression should have exactly one parent - you should not "reuse" a node by |
| having it appear more than once in the tree. The motivation for this |
| limitation is that when we optimize we modify nodes, so if they appear more |
| than once in the tree, a change in one place can appear in another |
| incorrectly. |
| * For similar reasons, nodes should not appear in more than one functions. |
| |
| ### Intrinsics |
| |
| Binaryen intrinsic functions look like calls to imports, e.g., |
| |
| ```wat |
| (import "binaryen-intrinsics" "foo" (func $foo)) |
| ``` |
| |
| Implementing them that way allows them to be read and written by other tools, |
| and it avoids confusing errors on a binary format error that could happen in |
| those tools if we had a custom binary format extension. |
| |
| An intrinsic method may be optimized away by the optimizer. If it is not, it |
| must be **lowered** before shipping the wasm, as otherwise it will look like a |
| call to an import that does not exist (and VMs will show an error on not having |
| a proper value for that import). That final lowering is *not* done |
| automatically. A user of intrinsics must run the pass for that explicitly, |
| because the tools do not know when the user intends to finish optimizing, as the |
| user may have a pipeline of multiple optimization steps, or may be doing local |
| experimentation, or fuzzing/reducing, etc. Only the user knows when the final |
| optimization happens before the wasm is "final" and ready to be shipped. Note |
| that, in general, some additional optimizations may be possible after the final |
| lowering, and so a useful pattern is to optimize once normally with intrinsics, |
| then lower them away, then optimize after that, e.g.: |
| |
| ```bash |
| wasm-opt input.wasm -o output.wasm -O --intrinsic-lowering -O |
| ``` |
| |
| Each intrinsic defines its semantics, which includes what the optimizer is |
| allowed to do with it and what the final lowering will turn it to. See |
| [intrinsics.h](https://github.com/WebAssembly/binaryen/blob/main/src/ir/intrinsics.h) |
| for the detailed definitions. A quick summary appears here: |
| |
| * `call.without.effects`: Similar to a `call_ref` in that it receives |
| parameters, and a reference to a function to call, and calls that function |
| with those parameters, except that the optimizer can assume the call has no |
| side effects, and may be able to optimize it out (if it does not have a |
| result that is used, generally). |
| |
| ## Tools |
| |
| This repository contains code that builds the following tools in `bin/` (see the [building instructions](#building)): |
| |
| * **`wasm-opt`**: Loads WebAssembly and runs Binaryen IR passes on it. |
| * **`wasm-as`**: Assembles WebAssembly in text format (currently S-Expression |
| format) into binary format (going through Binaryen IR). |
| * **`wasm-dis`**: Un-assembles WebAssembly in binary format into text format |
| (going through Binaryen IR). |
| * **`wasm2js`**: A WebAssembly-to-JS compiler. This is used by Emscripten to |
| generate JavaScript as an alternative to WebAssembly. |
| * **`wasm-reduce`**: A testcase reducer for WebAssembly files. Given a wasm file |
| that is interesting for some reason (say, it crashes a specific VM), |
| wasm-reduce can find a smaller wasm file that has the same property, which is |
| often easier to debug. See the |
| [docs](https://github.com/WebAssembly/binaryen/wiki/Fuzzing#reducing) |
| for more details. |
| * **`wasm-shell`**: A shell that can load and interpret WebAssembly code. It can |
| also run the spec test suite. |
| * **`wasm-emscripten-finalize`**: Takes a wasm binary produced by llvm+lld and |
| performs emscripten-specific passes over it. |
| * **`wasm-ctor-eval`**: A tool that can execute functions (or parts of functions) |
| at compile time. |
| * **`wasm-merge`**: Merges multiple wasm files into a single file, connecting |
| corresponding imports to exports as it does so. Like a bundler for JS, but |
| for wasm. |
| * **`wasm-metadce`**: A tool to remove parts of Wasm files in a flexible way |
| that depends on how the module is used. |
| * **`binaryen.js`**: A standalone JavaScript library that exposes Binaryen methods for [creating and optimizing Wasm modules](https://github.com/WebAssembly/binaryen/blob/main/test/binaryen.js/hello-world.js). For builds, see [binaryen.js on npm](https://www.npmjs.com/package/binaryen) (or download it directly from [GitHub](https://raw.githubusercontent.com/AssemblyScript/binaryen.js/master/index.js) or [unpkg](https://unpkg.com/binaryen@latest/index.js)). Minimal requirements: Node.js v15.8 or Chrome v75 or Firefox v78. |
| |
| All of the Binaryen tools are deterministic, that is, given the same inputs you should always get the same outputs. (If you see a case that behaves otherwise, please file an issue.) |
| |
| Usage instructions for each are below. |
| |
| ## Binaryen Optimizations |
| |
| Binaryen contains |
| [a lot of optimization passes](https://github.com/WebAssembly/binaryen/tree/main/src/passes) |
| to make WebAssembly smaller and faster. You can run the Binaryen optimizer by |
| using ``wasm-opt``, but also they can be run while using other tools, like |
| ``wasm2js`` and ``wasm-metadce``. |
| |
| * The default optimization pipeline is set up by functions like |
| [`addDefaultFunctionOptimizationPasses`](https://github.com/WebAssembly/binaryen/blob/369b8bdd3d9d49e4d9e0edf62e14881c14d9e352/src/passes/pass.cpp#L396). |
| * There are various |
| [pass options](https://github.com/WebAssembly/binaryen/blob/369b8bdd3d9d49e4d9e0edf62e14881c14d9e352/src/pass.h#L85) |
| that you can set, to adjust the optimization and shrink levels, whether to |
| ignore unlikely traps, inlining heuristics, fast-math, and so forth. See |
| ``wasm-opt --help`` for how to set them and other details. |
| |
| See each optimization pass for details of what it does, but here is a quick |
| overview of some of the relevant ones: |
| |
| * **CoalesceLocals** - Key "register allocation" pass. Does a live range |
| analysis and then reuses locals in order to minimize their number, as well as |
| to remove copies between them. |
| * **CodeFolding** - Avoids duplicate code by merging it (e.g. if two `if` arms |
| have some shared instructions at their end). |
| * **CodePushing** - "Pushes" code forward past branch operations, potentially |
| allowing the code to not be run if the branch is taken. |
| * **DeadArgumentElimination** - LTO pass to remove arguments to a function if it |
| is always called with the same constants. |
| * **DeadCodeElimination** |
| * **Directize** - Turn an indirect call into a normal call, when the table index |
| is constant. |
| * **DuplicateFunctionElimination** - LTO pass. |
| * **Inlining** - LTO pass. |
| * **LocalCSE** - Simple local common subexpression elimination. |
| * **LoopInvariantCodeMotion** |
| * **MemoryPacking** - Key "optimize data segments" pass that combines segments, |
| removes unneeded parts, etc. |
| * **MergeBlocks** - Merge a `block` to an outer one where possible, reducing |
| their number. |
| * **MergeLocals** - When two locals have the same value in part of their |
| overlap, pick in a way to help CoalesceLocals do better later (split off from |
| CoalesceLocals to keep the latter simple). |
| * **MinifyImportsAndExports** - Minifies them to "a", "b", etc. |
| * **OptimizeAddedConstants** - Optimize a load/store with an added constant into |
| a constant offset. |
| * **OptimizeInstructions** - Key peephole optimization pass with a constantly |
| increasing list of patterns. |
| * **PickLoadSigns** - Adjust whether a load is signed or unsigned in order to |
| avoid sign/unsign operations later. |
| * **Precompute** - Calculates constant expressions at compile time, using the |
| built-in interpreter (which is guaranteed to be able to handle any constant |
| expression). |
| * **ReReloop** - Transforms wasm structured control flow to a CFG and then goes |
| back to structured form using the Relooper algorithm, which may find more |
| optimal shapes. |
| * **RedundantSetElimination** - Removes a `local.set` of a value that is already |
| present in a local. (Overlaps with CoalesceLocals; this achieves the specific |
| operation just mentioned without all the other work CoalesceLocals does, and |
| therefore is useful in other places in the optimization pipeline.) |
| * **RemoveUnsedBrs** - Key "minor control flow optimizations" pass, including |
| jump threading and various transforms that can get rid of a `br` or `br_table` |
| (like turning a `block` with a `br` in the middle into an `if` when possible). |
| * **RemoveUnusedModuleElements** - "Global DCE", an LTO pass that removes |
| imports, functions, globals, etc., when they are not used. |
| * **ReorderFunctions** - Put more-called functions first, potentially allowing |
| the LEB emitted to call them to be smaller (in a very large program). |
| * **ReorderLocals** - Put more-used locals first, potentially allowing the LEB |
| emitted to use them to be smaller (in a very large function). After the |
| sorting, it also removes locals not used at all. |
| * **SimplifyGlobals** - Optimizes globals in various ways, for example, |
| coalescing them, removing mutability from a global never modified, applying a |
| constant value from an immutable global, etc. |
| * **SimplifyLocals** - Key "`local.get/set/tee`" optimization pass, doing things |
| like replacing a set and a get with moving the set’s value to the get (and |
| creating a tee) where possible. Also creates `block/if/loop` return values |
| instead of using a local to pass the value. |
| * **Vacuum** - Key "remove silly unneeded code" pass, doing things like removing |
| an `if` arm that has no contents, a drop of a constant value with no side |
| effects, a `block` with a single child, etc. |
| |
| "LTO" in the above means an optimization is Link Time Optimization-like in that |
| it works across multiple functions, but in a sense Binaryen is always "LTO" as |
| it usually is run on the final linked wasm. |
| |
| Advanced optimization techniques in the Binaryen optimizer include |
| [SSAification](https://github.com/WebAssembly/binaryen/blob/main/src/passes/SSAify.cpp), |
| [Flat IR](https://github.com/WebAssembly/binaryen/blob/main/src/ir/flat.h), and |
| [Stack/Poppy IR](https://github.com/WebAssembly/binaryen/blob/main/src/ir/stack-utils.h). |
| |
| See the |
| [Optimizer Cookbook wiki page](https://github.com/WebAssembly/binaryen/wiki/Optimizer-Cookbook) |
| for more on how to use the optimizer effectively. |
| |
| Binaryen also contains various passes that do other things than optimizations, |
| like |
| [legalization for JavaScript](https://github.com/WebAssembly/binaryen/blob/main/src/passes/LegalizeJSInterface.cpp), |
| [Asyncify](https://github.com/WebAssembly/binaryen/blob/main/src/passes/Asyncify.cpp), |
| etc. |
| |
| ## Building |
| |
| Binaryen uses git submodules (at time of writing just for gtest), so before you build you will have to initialize the submodules: |
| |
| ```bash |
| git submodule init |
| git submodule update |
| ``` |
| |
| After that you can build with CMake: |
| |
| ```bash |
| cmake . && make |
| ``` |
| |
| A C++17 compiler is required. On macOS, you need to install `cmake`, for example, via `brew install cmake`. Note that you can also use `ninja` as your generator: `cmake -G Ninja . && ninja`. |
| |
| To avoid the gtest dependency, you can pass `-DBUILD_TESTS=OFF` to cmake. |
| |
| Binaryen.js can be built using Emscripten, which can be installed via [the SDK](http://kripken.github.io/emscripten-site/docs/getting_started/downloads.html). |
| |
| - Building for Node.js: |
| ```bash |
| emcmake cmake . && emmake make binaryen_js |
| ``` |
| - Building for the browser: |
| ```bash |
| emcmake cmake -DBUILD_FOR_BROWSER=ON . && emmake make |
| ``` |
| |
| ### Visual C++ |
| |
| 1. Using the Microsoft Visual Studio Installer, install the "Visual C++ tools for CMake" component. |
| |
| 1. Generate the projects: |
| |
| ```bash |
| mkdir build |
| cd build |
| "%VISUAL_STUDIO_ROOT%\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" .. |
| ``` |
| |
| Substitute VISUAL_STUDIO_ROOT with the path to your Visual Studio |
| installation. In case you are using the Visual Studio Build Tools, the path |
| will be "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools". |
| |
| 1. From the Developer Command Prompt, build the desired projects: |
| |
| ```bash |
| msbuild binaryen.vcxproj |
| ``` |
| |
| CMake generates a project named "ALL_BUILD.vcxproj" for conveniently building all the projects. |
| |
| ## Releases |
| |
| Builds are distributed by the various toolchains that use Binaryen, like |
| Emscripten, `wasm-pack`, etc. There are also official releases on GitHub: |
| |
| https://github.com/WebAssembly/binaryen/releases |
| |
| Currently builds of the following platforms are included: |
| |
| * `Linux-x86_64` |
| * `Linux-arm64` |
| * `MacOS-x86_64` |
| * `MacOS-arm64` |
| * `Windows-x86_64` |
| * `Node.js` (experimental): A port of `wasm-opt` to JavaScript+WebAssembly. |
| Run `node wasm-opt.js` as a drop-in replacement for a native build of |
| `wasm-opt`, on any platform that Node.js runs on. Requires Node.js 18+ (for |
| Wasm EH and Wasm Threads). (Note that this build may also run in Deno, Bun, |
| or other JavaScript+WebAssembly environments, but is tested only on Node.js.) |
| |
| ## Running |
| |
| ### wasm-opt |
| |
| Run |
| |
| ```bash |
| bin/wasm-opt [.wasm or .wat file] [options] [passes, see --help] [--help] |
| ``` |
| |
| The wasm optimizer receives WebAssembly as input, and can run transformation |
| passes on it, as well as print it (before and/or after the transformations). For |
| example, try |
| |
| ```bash |
| bin/wasm-opt test/lit/passes/name-types.wast -all -S -o - |
| ``` |
| |
| That will output one of the test cases in the test suite. To run a |
| transformation pass on it, try |
| |
| ```bash |
| bin/wasm-opt test/lit/passes/name-types.wast --name-types -all -S -o - |
| ``` |
| |
| The `name-types` pass ensures each type has a name and renames exceptionally long type names. You can see |
| the change the transformation causes by comparing the output of the two commands. |
| |
| It's easy to add your own transformation passes to the shell, just add `.cpp` |
| files into `src/passes`, and rebuild the shell. For example code, take a look at |
| the [`name-types` pass](https://github.com/WebAssembly/binaryen/blob/main/src/passes/NameTypes.cpp). |
| |
| Some more notes: |
| |
| * See `bin/wasm-opt --help` for the full list of options and passes. |
| * Passing `--debug` will emit some debugging info. Individual debug channels |
| (defined in the source code via `#define DEBUG_TYPE xxx`) can be enabled by |
| passing them as list of comma-separated strings. For example: `bin/wasm-opt |
| --debug=binary`. These debug channels can also be enabled via the |
| `BINARYEN_DEBUG` environment variable. |
| |
| ### wasm2js |
| |
| Run |
| |
| ```bash |
| bin/wasm2js [input.wasm file] |
| ``` |
| |
| This will print out JavaScript to the console. |
| |
| For example, try |
| |
| ```bash |
| bin/wasm2js test/hello_world.wat |
| ``` |
| |
| That output contains |
| |
| ```js |
| function add(x, y) { |
| x = x | 0; |
| y = y | 0; |
| return x + y | 0 | 0; |
| } |
| ``` |
| |
| as a translation of |
| |
| ```wat |
| (func $add (; 0 ;) (type $0) (param $x i32) (param $y i32) (result i32) |
| (i32.add |
| (local.get $x) |
| (local.get $y) |
| ) |
| ) |
| ``` |
| |
| wasm2js's output is in ES6 module format - basically, it converts a wasm |
| module into an ES6 module (to run on older browsers and Node.js versions |
| you can use Babel etc. to convert it to ES5). Let's look at a full example |
| of calling that hello world wat; first, create the main JS file: |
| |
| ```js |
| // main.mjs |
| import { add } from "./hello_world.mjs"; |
| console.log('the sum of 1 and 2 is:', add(1, 2)); |
| ``` |
| |
| The run this (note that you need a new enough Node.js with ES6 module |
| support): |
| |
| ```bash |
| $ bin/wasm2js test/hello_world.wat -o hello_world.mjs |
| $ node --experimental-modules main.mjs |
| the sum of 1 and 2 is: 3 |
| ``` |
| |
| Things keep to in mind with wasm2js's output: |
| |
| * You should run wasm2js with optimizations for release builds, using `-O` |
| or another optimization level. That will optimize along the entire pipeline |
| (wasm and JS). It won't do everything a JS minifer would, though, like |
| minify whitespace, so you should still run a normal JS minifer afterwards. |
| * It is not possible to match WebAssembly semantics 100% precisely with fast |
| JavaScript code. For example, every load and store may trap, and to make |
| JavaScript do the same we'd need to add checks everywhere, which would be |
| large and slow. Instead, wasm2js assumes loads and stores do not trap, that |
| int/float conversions do not trap, and so forth. There may also be slight |
| differences in corner cases of conversions, like non-trapping float to int. |
| |
| ### wasm-ctor-eval |
| |
| `wasm-ctor-eval` executes functions, or parts of them, at compile time. |
| After doing so it serializes the runtime state into the wasm, which is like |
| taking a "snapshot". When the wasm is later loaded and run in a VM, it will |
| continue execution from that point, without re-doing the work that was already |
| executed. |
| |
| For example, consider this small program: |
| |
| ```wat |
| (module |
| ;; A global variable that begins at 0. |
| (global $global (mut i32) (i32.const 0)) |
| |
| (import "import" "import" (func $import)) |
| |
| (func "main" |
| ;; Set the global to 1. |
| (global.set $global |
| (i32.const 1)) |
| |
| ;; Call the imported function. This *cannot* be executed at |
| ;; compile time. |
| (call $import) |
| |
| ;; We will never get to this point, since we stop at the |
| ;; import. |
| (global.set $global |
| (i32.const 2)) |
| ) |
| ) |
| ``` |
| |
| We can evaluate part of it at compile time like this: |
| |
| ```bash |
| wasm-ctor-eval input.wat --ctors=main -S -o - |
| ``` |
| |
| This tells it that there is a single function that we want to execute ("ctor" |
| is short for "global constructor", a name that comes from code that is executed |
| before a program's entry point) and then to print it as text to `stdout`. The |
| result is this: |
| |
| ```bash |
| trying to eval main |
| ...partial evalling successful, but stopping since could not eval: call import: import.import |
| ...stopping |
| (module |
| (type $none_=>_none (func)) |
| (import "import" "import" (func $import)) |
| (global $global (mut i32) (i32.const 1)) |
| (export "main" (func $0_0)) |
| (func $0_0 |
| (call $import) |
| (global.set $global |
| (i32.const 2) |
| ) |
| ) |
| ) |
| ``` |
| |
| The logging shows us managing to eval part of `main()`, but not all of it, as |
| expected: We can eval the first `global.get`, but then we stop at the call to |
| the imported function (because we don't know what that function will be when the |
| wasm is actually run in a VM later). Note how in the output wasm the global's |
| value has been updated from 0 to 1, and that the first `global.get` has been |
| removed: the wasm is now in a state that, when we run it in a VM, will seamlessly |
| continue to run from the point at which `wasm-ctor-eval` stopped. |
| |
| In this tiny example we just saved a small amount of work. How much work can be |
| saved depends on your program. (It can help to do pure computation up front, and |
| leave calls to imports to as late as possible.) |
| |
| Note that `wasm-ctor-eval`'s name is related to global constructor functions, |
| as mentioned earlier, but there is no limitation on what you can execute here. |
| Any export from the wasm can be executed, if its contents are suitable. For |
| example, in Emscripten `wasm-ctor-eval` is even run on `main()` when possible. |
| |
| ### wasm-merge |
| |
| `wasm-merge` combines wasm files together. For example, imagine you have a |
| project that uses wasm files from multiple toolchains. Then it can be helpful to |
| merge them all into a single wasm file before shipping, since in a single wasm |
| file the calls between the modules become just normal calls inside a module, |
| which allows them to be inlined, dead code eliminated, and so forth, potentially |
| improving speed and size. |
| |
| `wasm-merge` operates on normal wasm files. It differs from `wasm-ld` in that |
| respect, as `wasm-ld` operates on wasm *object* files. `wasm-merge` can help |
| in multi-toolchain situations where at least one of the toolchains does not use |
| wasm object files. |
| |
| For example, imagine we have these two wasm files: |
| |
| ```wat |
| ;; a.wasm |
| (module |
| (import "second" "bar" (func $second.bar)) |
| |
| (export "main" (func $func)) |
| |
| (func $func |
| (call $second.bar) |
| ) |
| ) |
| ``` |
| |
| ```wat |
| ;; b.wasm |
| (module |
| (import "outside" "log" (func $log (param i32))) |
| |
| (export "bar" (func $func)) |
| |
| (func $func |
| (call $log |
| (i32.const 42) |
| ) |
| ) |
| ) |
| ``` |
| |
| The filenames on your local drive are `a.wasm` and `b.wasm`, but for merging / |
| bundling purposes let's say that the first is known as `"first"` and the second |
| as `"second"`. That is, we want the first module's import of `"second.bar"` to |
| call the function `$func` in the second module. Here is a wasm-merge command for |
| that: |
| |
| ```bash |
| wasm-merge a.wasm first b.wasm second -o output.wasm |
| ``` |
| |
| We give it the first wasm file, then its name, and then the second wasm file |
| and then its name. The merged output is this: |
| |
| ```wat |
| (module |
| (import "outside" "log" (func $log (param i32))) |
| |
| (export "main" (func $func)) |
| (export "bar" (func $func_2)) |
| |
| (func $func |
| (call $func_2) |
| ) |
| |
| (func $func_2 |
| (call $log |
| (i32.const 42) |
| ) |
| ) |
| ) |
| ``` |
| |
| `wasm-merge` combined the two files into one, merging their functions, imports, |
| etc., all while fixing up name conflicts and connecting corresponding imports to |
| exports. In particular, note how `$func` calls `$func_2`, which is exactly what |
| we wanted: `$func_2` is the function from the second module (renamed to avoid a |
| name collision). |
| |
| Note that the wasm output in this example could benefit from additional |
| optimization. First, the call to `$func_2` can now be easily inlined, so we can |
| run `wasm-opt -O3` to do that for us. Also, we may not need all the imports and |
| exports, for which we can run |
| [wasm-metadce](https://github.com/WebAssembly/binaryen/wiki/Pruning-unneeded-code-in-wasm-files-with-wasm-metadce#example-pruning-exports). |
| A good workflow could be to run `wasm-merge`, then `wasm-metadce`, then finish |
| with `wasm-opt`. |
| |
| `wasm-merge` is kind of like a bundler for wasm files, in the sense of a "JS |
| bundler" but for wasm. That is, with the wasm files above, imagine that we had |
| this JS code to instantiate and connect them at runtime: |
| |
| ```js |
| // Compile the first module. |
| var first = await fetch("a.wasm"); |
| first = new WebAssembly.Module(first); |
| |
| // Compile the first module. |
| var second = await fetch("b.wasm"); |
| second = new WebAssembly.Module(second); |
| |
| // Instantiate the second, with a JS import. |
| second = new WebAssembly.Instance(second, { |
| outside: { |
| log: (value) => { |
| console.log('value:', value); |
| } |
| } |
| }); |
| |
| // Instantiate the first, importing from the second. |
| first = new WebAssembly.Instance(first, { |
| second: second.exports |
| }); |
| |
| // Call the main function. |
| first.exports.main(); |
| ``` |
| |
| What `wasm-merge` does is basically what that JS does: it hooks up imports to |
| exports, resolving names using the module names you provided. That is, by |
| running `wasm-merge` we are moving the work of connecting the modules from |
| runtime to compile time. As a result, after running `wasm-merge` we need a lot |
| less JS to get the same result: |
| |
| ```js |
| // Compile the single module. |
| var merged = await fetch("merged.wasm"); |
| merged = new WebAssembly.Module(merged); |
| |
| // Instantiate it with a JS import. |
| merged = new WebAssembly.Instance(merged, { |
| outside: { |
| log: (value) => { |
| console.log('value:', value); |
| } |
| } |
| }); |
| |
| // Call the main function. |
| merged.exports.main(); |
| ``` |
| |
| We still need to fetch and compile the merged wasm, and to provide it the JS |
| import, but the work to connect two wasm modules is not needed any more. |
| |
| #### Handling exports |
| |
| By default `wasm-merge` errors if there are overlapping export names. That is, |
| `wasm-merge` will automatically handle overlapping function names and so forth, |
| because those are not externally visible (the code still behaves the same), but |
| if we renamed exports then the outside would need to be modified to expect the |
| new export names, and so we error instead on such name conflicts. |
| |
| If you do want exports to be renamed, run `wasm-merge` with |
| `--rename-export-conflicts`. Later exports will have a suffix appended to them |
| to ensure they do not overlap with previous exports. The suffixes are |
| deterministic, so once you see what they are you can call them from the outside. |
| |
| Another option is to use `--skip-export-conflicts` which will simply skip later |
| exports that have conflicting names. For example, this can be useful in the |
| case where the first module is the only one that interacts with the outside and |
| the later modules just interact with the first module. |
| |
| #### Features |
| |
| `wasm-merge` uses the multi-memory and multi-table features. That is, if |
| multiple input modules each have a memory then the output wasm will have several |
| memories, and will depend on the multi-memory feature, which means that older |
| wasm VMs might not be able to run the wasm. (As a workaround for such older VMs |
| you can run `wasm-opt --multi-memory-lowering` to lower multiple memories into a |
| single one.) |
| |
| ## Testing |
| |
| ```bash |
| ./check.py |
| ``` |
| |
| (or `python check.py`) will run `wasm-shell`, `wasm-opt`, etc. on the testcases in `test/`, and verify their outputs. |
| |
| The `check.py` script supports some options: |
| |
| ```bash |
| ./check.py [--interpreter=/path/to/interpreter] [TEST1] [TEST2].. |
| ``` |
| |
| * If an interpreter is provided, we run the output through it, checking for |
| parse errors. |
| * If tests are provided, we run exactly those. If none are provided, we run |
| them all. To see what tests are available, run `./check.py --list-suites`. |
| * Some tests require `emcc` or `nodejs` in the path. They will not run if the |
| tool cannot be found, and you'll see a warning. |
| * We have tests from upstream in `tests/spec`, in git submodules. Running |
| `./check.py` should update those. |
| |
| Note that we are trying to gradually port the legacy wasm-opt tests to use `lit` |
| and `filecheck` as we modify them. For `passes` tests that output wast, this |
| can be done automatically with `scripts/port_passes_tests_to_lit.py` and for |
| non-`passes` tests that output wast, see |
| https://github.com/WebAssembly/binaryen/pull/4779 for an example of how to do a |
| simple manual port. |
| |
| For lit tests the test expectations (the CHECK lines) can often be automatically |
| updated as changes are made to binaryen. See `scripts/update_lit_checks.py`. |
| |
| Non-lit tests can also be automatically updated in most cases. See |
| `scripts/auto_update_tests.py`. |
| |
| ### Setting up dependencies |
| |
| ```bash |
| ./third_party/setup.py [mozjs|v8|wabt|all] |
| ``` |
| |
| (or `python third_party/setup.py`) installs required dependencies like the SpiderMonkey JS shell, the V8 JS shell |
| and WABT in `third_party/`. Other scripts automatically pick these up when installed. |
| |
| Run `pip3 install -r requirements-dev.txt` to get the requirements for the `lit` |
| tests. Note that you need to have the location `pip` installs to in your `$PATH` |
| (on linux, `~/.local/bin`). |
| |
| ### Fuzzing |
| |
| ```bash |
| ./scripts/fuzz_opt.py [--binaryen-bin=build/bin] |
| ``` |
| |
| (or `python scripts/fuzz_opt.py`) will run various fuzzing modes on random inputs with random passes until it finds |
| a possible bug. See [the wiki page](https://github.com/WebAssembly/binaryen/wiki/Fuzzing) for all the details. |
| |
| ## Design Principles |
| |
| * **Interned strings for names**: It's very convenient to have names on nodes, |
| instead of just numeric indices etc. To avoid most of the performance |
| difference between strings and numeric indices, all strings are interned, |
| which means there is a single copy of each string in memory, string |
| comparisons are just a pointer comparison, etc. |
| * **Allocate in arenas**: Based on experience with other |
| optimizing/transformating toolchains, it's not worth the overhead to |
| carefully track memory of individual nodes. Instead, we allocate all elements |
| of a module in an arena, and the entire arena can be freed when the module is |
| no longer needed. |
| |
| ## Debug Info Support |
| |
| ### Source Maps |
| |
| Binaryen can read and write source maps (see the `-ism` and `-osm` flags to |
| `wasm-opt`). It can also read and read source map annotations in the text |
| format, that is, |
| |
| ```wat |
| ;;@ src.cpp:100:33 |
| (i32.const 42) |
| ``` |
| |
| That 42 constant is annotated as appearing in a file called `src.cpp` at line |
| `100` and column `33`. Source maps and text format annotations are |
| interchangeable, that is, they both lead to the same IR representation, so you |
| can start with an annotated wat and have Binaryen write that to a binary + a |
| source map file, or read a binary + source map file and print text which will |
| contain those annotations. |
| |
| The IR representation of source map info is simple: in each function we have a |
| map of expressions to their locations. Optimization passes should update the |
| map as relevant. Often this "just works" because the optimizer tries to reuse |
| nodes when possible, so they keep the same debug info. |
| |
| #### Shorthand notation |
| |
| The text format annotations support a shorthand in which repeated annotations |
| are not necessary. For example, children are tagged with the debug info of the |
| parent, if they have no annotation of their own: |
| |
| ```wat |
| ;;@ src.cpp:100:33 |
| (i32.add |
| (i32.const 41) ;; This receives an annotation of src.cpp:100:33 |
| ;;@ src.cpp:111:44 |
| (i32.const 1) |
| ) |
| ``` |
| |
| The first const will have debug info identical to the parent, because it has |
| none specified, and generally such nesting indicates a "bundle" of instructions |
| that all implement the same source code. |
| |
| Note that text printing will not emit such repeated annotations, which can be |
| confusing. To print out all the annotations, set `BINARYEN_PRINT_FULL=1` in the |
| environment. That will print this for the above `add`: |
| |
| ```wat |
| [i32] ;;@ src.cpp:100:33 |
| (i32.add |
| [i32] ;;@ src.cpp:100:33 |
| (i32.const 41) |
| [i32] ;;@ src.cpp:111:44 |
| (i32.const 1) |
| ) |
| ``` |
| |
| (full print mode also adds a `[type]` for each expression, right before the |
| debug location). |
| |
| The debug information is also propagated from an expression to its |
| next sibling: |
| ```wat |
| ;;@ src.cpp:100:33 |
| (local.set $x |
| (i32.const 0) |
| ) |
| (local.set $y ;; This receives an annotation of src.cpp:100:33 |
| (i32.const 0) |
| ) |
| ``` |
| |
| You can prevent the propagation of debug info by explicitly mentioning |
| that an expression has not debug info using the annotation `;;@` with |
| nothing else: |
| ```wat |
| ;;@ src.cpp:100:33 |
| (local.set $x |
| ;;@ |
| (i32.const 0) ;; This does not receive any annotation |
| ) |
| ;;@ |
| (local.set $y ;; This does not receive any annotation |
| (i32.const 7) |
| ) |
| ``` |
| This stops the propagatation to children and siblings as well. So, |
| expression `(i32.const 7)` does not have any debug info either. |
| |
| There is no shorthand in the binary format. That is, roundtripping (writing and |
| reading) through a binary + source map should not change which expressions have |
| debug info on them or the contents of that info. |
| |
| #### Implementation Details |
| |
| The [source maps format](https://sourcemaps.info/spec.html) defines a mapping |
| using *segments*, that is, if a segment starts at binary offset 10 then it |
| applies to all instructions at that offset and until another segment begins (or |
| the end of the input is reached). Binaryen's IR represents a mapping from |
| expressions to locations, as mentioned, so we need to map to and from the |
| segment-based format when writing and reading source maps. |
| |
| That is mostly straightforward, but one thing we need to do is to handle the |
| lack of debug info in between things that have it. If we have `A B C` where `B` |
| lacks debug info, then just emitting a segment for `A` and `C` would lead `A`'s |
| segment to also cover `B`, since in source maps segments do not have a size - |
| rather they end when a new segment begins. To avoid `B` getting smeared in this |
| manner, we emit a source maps entry to `B` of size 1, which just marks the |
| binary offset it has, and without the later 3 fields of the source file, line |
| number, and column. (This appears to be the intent of the source maps spec, and |
| works in browsers and tools.) |
| |
| ### DWARF |
| |
| Binaryen also has optional support for DWARF. This primarily just tracks the |
| locations of expressions and rewrites the DWARF's locations accordingly; it does |
| not handle things like re-indexing of locals, and so passes that might break |
| DWARF are disabled by default. As a result, this mode is not suitable for a |
| fully optimized release build, but it can be useful for local debugging. |
| |
| ## FAQ |
| |
| * Why the weird name for the project? |
| |
| Binaryen's name was inspired by *Emscripten*'s: Emscripten's name |
| [suggests](https://en.wikipedia.org/wiki/Lisa_the_Iconoclast#Embiggen_and_cromulent) |
| it converts something into a **script** - specifically *JavaScript* - and |
| Binaryen's suggests it converts something into a **binary** - specifically |
| *WebAssembly*. Binaryen began as Emscripten's WebAssembly generation and |
| optimization tool, so the name fit as it moved Emscripten from something that |
| emitted the text-based format JavaScript (as it did from its early days) to the |
| binary format WebAssembly (which it has done since WebAssembly launched). |
| |
| "Binaryen" is pronounced [in the same manner](https://www.makinggameofthrones.com/production-diary/2011/2/11/official-pronunciation-guide-for-game-of-thrones.html#:~:text=Targaryen%20%2D%20AIR%2Deez-,Tar%2DGAIR%2Dee%2Din,-Alliser%20Thorne%20%2D%20AL) |
| as |
| "[Targaryen](https://en.wikipedia.org/wiki/List_of_A_Song_of_Ice_and_Fire_characters#House_Targaryen)". |
| |
| * Does it compile under Windows and/or Visual Studio? |
| |
| Yes, it does. Here's a step-by-step [tutorial][win32] on how to compile it |
| under **Windows 10 x64** with with **CMake** and **Visual Studio 2015**. |
| However, Visual Studio 2017 may now be required. Help would be appreciated on |
| Windows and OS X as most of the core devs are on Linux. |
| |
| [compiling to WebAssembly]: https://github.com/WebAssembly/binaryen/wiki/Compiling-to-WebAssembly-with-Binaryen |
| [win32]: https://github.com/brakmic/bazaar/blob/master/webassembly/COMPILING_WIN32.md |
| [C API]: https://github.com/WebAssembly/binaryen/wiki/Compiling-to-WebAssembly-with-Binaryen#c-api |
| [control flow graph]: https://github.com/WebAssembly/binaryen/wiki/Compiling-to-WebAssembly-with-Binaryen#cfg-api |
| [JS_API]: https://github.com/WebAssembly/binaryen/wiki/binaryen.js-API |
| [compile_to_wasm]: https://github.com/WebAssembly/binaryen/wiki/Compiling-to-WebAssembly-with-Binaryen#what-do-i-need-to-have-in-order-to-use-binaryen-to-compile-to-webassembly |
| [backend]: https://kripken.github.io/talks/binaryen.html#/9 |
| [minification]: https://kripken.github.io/talks/binaryen.html#/2 |
| [unreachable]: https://github.com/WebAssembly/binaryen/issues/903 |
| [binaryen_ir]: https://github.com/WebAssembly/binaryen/issues/663 |
| [stringref proposal]: https://github.com/WebAssembly/stringref/blob/main/proposals/stringref/Overview.md |
| [js string builtins]: https://github.com/WebAssembly/js-string-builtins/blob/main/proposals/js-string-builtins/Overview.md |