Leaving the Sea of Nodes: V8's Shift to Turboshaft
For years, V8's top-tier optimizing compiler, Turbofan, relied on an intermediate representation (IR) called Sea of Nodes (SoN) – a rare choice among production compilers. However, about three years ago, the team began transitioning away from SoN to a more traditional Control-Flow Graph (CFG) IR named Turboshaft. This shift aimed to address long-standing limitations inherited from the earlier Crankshaft compiler and to improve performance, maintainability, and flexibility. Below, we answer key questions about this major architectural change.
What is the Sea of Nodes intermediate representation and why did V8's Turbofan initially adopt it?
Sea of Nodes (SoN) is an intermediate representation (IR) that merges control flow and data dependencies into a unified graph. Unlike traditional control-flow graphs (CFG), SoN allows operations to be freely scheduled and reordered, enabling aggressive optimizations. V8's Turbofan compiler adopted SoN because it promised to overcome the rigidity of its predecessor, Crankshaft, by allowing dynamic control flow creation during lowering and better handling of speculative optimizations. SoN also aimed to reduce the performance cliffs and deoptimization loops that plagued Crankshaft. However, the complexity of maintaining and debugging SoN eventually outweighed its benefits, leading V8 to develop Turboshaft, a simpler CFG-based IR that retains the desired flexibility without the overhead.
What were the major shortcomings of V8's earlier compiler Crankshaft?
Crankshaft, V8's first optimizing compiler, used a CFG-based IR but suffered from several critical issues. First, it contained excessive hand-written assembly code – every new operator required manual translation to assembly for four architectures (x64, ia32, arm, arm64), slowing development. Second, it struggled with asm.js, an important high-performance JavaScript subset at the time. Third, control flow was finalized at graph building time, preventing the introduction of additional control flow during lowering – a major limitation for common compiler transformations like converting high-level operations into conditional branches. Fourth, try-catch statements were unsupported despite months of effort by multiple engineers. Fifth, it suffered from severe performance cliffs where using specific features could cause 100x slowdowns. Finally, deoptimization loops were frequent: Crankshaft would reoptimize functions with the same speculative assumptions that had already failed, leading to endless cycles.
How does the new Turboshaft IR differ from Sea of Nodes?
Turboshaft is a Control-Flow Graph (CFG) intermediate representation with explicit basic blocks and edges that define the program's execution order. In contrast, Sea of Nodes represents both control and data in a single graph without predetermined scheduling. Turboshaft simplifies analysis and transformation by making control flow explicit, which reduces compiler complexity and debugging difficulty. It also allows introducing control flow during lowering – a key feature that Crankshaft lacked and that SoN provided but with added overhead. Turboshaft is designed to be more maintainable and to avoid the performance cliffs that plagued earlier systems, while still enabling powerful optimizations. The WebAssembly pipeline already uses Turboshaft entirely, and the JavaScript backend of Turbofan has fully transitioned to it.
What specific problems with Crankshaft led to the development of Turbofan and eventually Turboshaft?
The three most critical problems were the inability to introduce control flow during lowering, the lack of try-catch support, and the performance cliffs/deoptimization loops. The control-flow limitation meant that high-level operations (e.g., JSAdd(x, y)) could not be lowered to conditional logic like if (x is String and y is String) { StringAdd(x, y) } else { ... }. Try-catch support was deemed essential for modern JavaScript but proved impossible to implement within Crankshaft's architecture. The performance cliffs made code optimization unpredictable for developers. Turbofan was created to solve these issues, initially using Sea of Nodes. However, over time, the complexity of SoN led to similar problems in maintainability, prompting the switch to Turboshaft. The transition shows an evolution from a rigid CFG (Crankshaft) to a flexible graph (SoN) to a refined CFG (Turboshaft) that balances simplicity with expressiveness.
What is the current status of V8's transition from Sea of Nodes to Turboshaft?
As of 2024, the entire JavaScript backend of Turbofan uses Turboshaft. The WebAssembly pipeline also relies fully on Turboshaft. Two parts of Turbofan still use some Sea of Nodes: the builtin pipeline, which is being slowly replaced by Turboshaft, and the frontend of the JavaScript pipeline, which is being replaced by Maglev – another CFG-based IR that integrates with Turboshaft. The migration is nearly complete, with SoN only remaining in legacy paths that are actively being phased out. This transition has improved code quality, reduced performance cliffs, and made future optimizations easier to implement.
Why does V8 still use Sea of Nodes in some parts of its pipeline?
Sea of Nodes (SoN) persists in two specific areas: the builtin pipeline and the frontend of the JavaScript pipeline. The builtin pipeline handles internal V8 functions (built-ins) that are not yet optimized with Turboshaft; migration is underway but takes time due to the complexity of these operations. The JavaScript pipeline frontend, which parses and initializes JavaScript code, is being replaced by Maglev, a new CFG-based IR designed for simpler and faster compilation. Maglev will eventually replace the SoN-based frontend entirely. Until these replacements are complete, SoN remains for backward compatibility and to ensure a smooth transition. The goal is to eliminate SoN completely, unifying V8's compiler infrastructure under CFG-based IRs (Turboshaft and Maglev) for consistency and maintainability.