Executing WebAssembly in Chromium with the Liftoff Compiler
This article explains how the Chromium web browser rapidly executes WebAssembly (Wasm) modules using Liftoff, the baseline compiler within the V8 engine. It covers the tiered compilation pipeline, the mechanics of Liftoff's single-pass code generation, how it manages memory and registers for instant startup, and how it transitions to the optimizing compiler for peak performance.
The V8 Tiered Compilation Pipeline
Chromium relies on the V8 engine to run both JavaScript and WebAssembly. To balance fast application startup times with high execution speeds, V8 uses a tiered compilation strategy for WebAssembly.
Liftoff is the first tier in this pipeline. Its sole responsibility is to translate WebAssembly bytecode into native machine code as fast as possible. By prioritizing compilation speed over code optimization, Liftoff ensures that web applications can begin executing almost immediately after loading.
Streaming and Single-Pass Compilation
Traditional compilers read code multiple times to build complex internal data structures, such as Abstract Syntax Trees (AST) or Intermediate Representations (IR), to analyze and optimize the program. Liftoff skips these steps entirely.
Liftoff is a single-pass compiler. It reads the WebAssembly bytecode once, from beginning to end, and translates it directly into machine code. Because it operates in a single pass, Chromium uses Liftoff for streaming compilation. As the browser downloads the WebAssembly module bytes from the network, it feeds them directly into Liftoff. Compilation happens concurrently with the download, meaning the module is often ready to execute the exact millisecond the download finishes.
The Virtual Stack and Register Allocation
WebAssembly is designed as a stack machine, meaning its instructions push values onto an operand stack and pop them off to perform operations. However, constantly reading and writing to a physical stack in system memory is slow on modern CPUs, which rely on hardware registers for fast data access.
To generate reasonably fast code without doing multi-pass analysis, Liftoff uses a "virtual stack" during its single compilation pass. Here is how it works:
- Tracking State: As Liftoff reads each bytecode instruction, it updates an internal data structure (the virtual stack) that keeps track of where values are located.
- Deferred Execution: Instead of immediately emitting machine code to push a value to memory, Liftoff simply notes on its virtual stack that a value exists and assigns it to an available CPU register.
- Consuming Values: When Liftoff encounters an instruction that uses those values (like an addition operation), it checks the virtual stack, sees which registers hold the values, and emits a single CPU instruction to add those registers together.
- Spilling: Liftoff only emits instructions to write data to the actual memory stack if the CPU runs out of free registers, a process known as "spilling."
Direct Machine Code Emission
As Liftoff iterates through the bytecode and manipulates the virtual stack, it directly emits native machine code tailored to the user's specific CPU architecture (such as x86-64 or ARM64).
Because WebAssembly bytecode is already structured and validated before it reaches the compiler, Liftoff does not need to perform complex type checking. It simply maps the WebAssembly instructions and virtual stack states to the corresponding CPU instructions. This stripped-down approach allows Liftoff to compile code at speeds of tens of megabytes per second.
Tiering Up to TurboFan
Once Liftoff finishes compiling a WebAssembly function, Chromium executes the generated machine code immediately. While the code runs, V8's execution pipeline moves to the next phase to improve performance.
Because Liftoff does not perform advanced optimizations (like loop unrolling or instruction reordering), its output is slower than fully optimized code. While the user interacts with the application running on Liftoff code, V8 sends the WebAssembly bytecode to background threads running TurboFan, V8's optimizing compiler.
TurboFan takes significantly longer to compile the code because it builds an Intermediate Representation and runs heavy optimization passes. As soon as TurboFan finishes compiling a function, V8 dynamically replaces the Liftoff machine code with the newly optimized TurboFan machine code in memory. This process, known as "tiering up," gives Chromium users the benefit of instant startup via Liftoff and maximum execution speed via TurboFan.