docs/runtime/tiering.md
This document explains how V8 manages the transition of functions between different execution tiers (Interpretation, Baseline, Mid-Tier, and Top-Tier optimization) and the mechanism used to trigger these transitions.
V8 uses a multi-tiered execution strategy to balance startup time and peak performance:
To decide when to promote (or "tier up") a function to a higher tier, V8 uses an Interrupt Budget mechanism.
FeedbackCell (specifically interrupt_budget_).All execution tiers (Ignition, Sparkplug, Maglev) decrement the interrupt budget at strategic points:
In src/interpreter/interpreter-assembler.cc, Ignition decrements the budget in DecreaseInterruptBudget which is called by JumpBackward (for loops) and UpdateInterruptBudgetOnReturn (for returns).
In architecture-specific files like src/baseline/x64/baseline-assembler-x64-inl.h, Sparkplug decrements the budget in AddToInterruptBudgetAndJumpIfNotExceeded (using negative weights) at returns and loop back-edges.
In Maglev IR, these are represented by nodes like ReduceInterruptBudgetForLoop and ReduceInterruptBudgetForReturn.
In src/maglev/x64/maglev-ir-x64.cc, the budget reduction is implemented by subtracting the amount from the FeedbackCell:
void GenerateReduceInterruptBudget(MaglevAssembler* masm, Node* node,
Register feedback_cell,
ReduceInterruptBudgetType type, int amount) {
MaglevAssembler::TemporaryRegisterScope temps(masm);
__ subl(
FieldOperand(feedback_cell, offsetof(FeedbackCell, interrupt_budget_)),
Immediate(amount));
ZoneLabelRef done(masm);
__ JumpToDeferredIf(less, HandleInterruptsAndTiering, done, node, type);
__ bind(*done);
}
If the result of the subtraction is negative (less), it jumps to deferred code (HandleInterruptsAndTiering).
When the budget is exceeded, V8 calls into the C++ runtime. Each tier has its own set of runtime functions:
Runtime::kBytecodeBudgetInterruptWithStackCheck_Ignition and Runtime::kBytecodeBudgetInterrupt_Ignition.Runtime::kBytecodeBudgetInterruptWithStackCheck_Sparkplug and Runtime::kBytecodeBudgetInterrupt_Sparkplug.Runtime::kBytecodeBudgetInterruptWithStackCheck_Maglev and Runtime::kBytecodeBudgetInterrupt_Maglev.The "WithStackCheck" variants are typically used at loop back-edges to handle both interrupts (e.g., GC requests, debugging pauses) and stack limit checks simultaneously.
All these runtime functions eventually call into the TieringManager via TieringManager::OnInterruptTick.
The TieringManager inspects the function's profiling data (feedback vector, invocation count, etc.) to make a decision:
Once the background compilation finishes, V8 updates the function's entry in the JS Dispatch Table.
In modern V8 (especially with the V8 Sandbox enabled), a JSFunction does not directly point to its Code object. Instead, it contains a JSDispatchHandle (an index into the JSDispatchTable).
JSDispatchTable holds the actual pointer to the Code (and the entry point).JSFunction object itself. Instead, it updates the entry in the JSDispatchTable pointed to by the function's handle.Future calls to the function will automatically use the new code because they go through the same dispatch handle.
OSR is a technique that allows V8 to switch execution from unoptimized code (Ignition) to optimized code (Maglev/TurboFan) while the function is currently executing (on the stack).
If a function contains a very long-running loop but is otherwise not called frequently, the budget interrupt at the return point will not trigger optimization soon enough. The loop back-edge budget reduction handles this by triggering optimization while still in the loop.
src/maglev/maglev-compiler.cc: Maglev compilation pipeline.src/execution/tiering-manager.cc: V8's tiering manager.