| # Function Architecture in V8 |
| |
| To execute JavaScript functions efficiently, V8 splits function data into **context-independent** and **context-dependent** parts. This separation allows sharing of heavy assets (like bytecode) while allowing specific instances (closures) to maintain their own state and feedback. |
| |
| This document describes this architecture and how functions are chained together both statically and dynamically. |
| |
| ## Context-Independent Data: `SharedFunctionInfo` |
| |
| Data that does not change regardless of where or how many times a function is instantiated is stored in the `SharedFunctionInfo` (SFI). |
| |
| * **Purpose**: To save memory by sharing read-only information across all instances of the function, even across different `NativeContext`s. |
| * **Key Contents**: |
| * **Bytecode**: The executable bytecode generated by Ignition. |
| * **`ScopeInfo`**: The lexical structure of the scope (see [Scopes and ScopeInfos](scopes-and-scope-infos.md)). |
| * **Source Positions**: Mapping bytecode to source code lines. |
| * **`FeedbackMetadata`**: Describes the shape and size of the `FeedbackVector` that needs to be allocated for this function. |
| |
| ### SFI Chaining via Bytecode |
| `SharedFunctionInfo` objects do not form a direct linked list. Instead, they form a tree structure embedded within the bytecode. |
| When a function contains a nested function definition, the outer function's `BytecodeArray` is responsible for creating the closure for the inner function. |
| 1. **Inner SFI in Constant Pool**: The SFI for the inner function is created during parsing/compilation of the outer function and is placed in the **Constant Pool** of the outer function's `BytecodeArray`. |
| 2. **`CreateClosure` Bytecode**: The outer function's bytecode contains a `CreateClosure` instruction at the point where the inner function is defined/created. |
| 3. **Reference**: The `CreateClosure` bytecode takes an operand that is the index of the inner SFI in the constant pool. |
| |
| ## Context-Dependent Data |
| |
| Data that depends on the specific execution context or function instance is stored in objects linked from the `JSFunction`. |
| |
| ### `JSFunction` |
| A `JSFunction` represents an actual JavaScript function object available to the user code (a closure). |
| * **Purpose**: To represent a specific instance of a function with its own scope chain and execution state. |
| * **Key Contents**: |
| * **Link to `SharedFunctionInfo`**: Points to the shared data. |
| * **`Context`**: Points to the execution context (lexical environment) where the function was created. This is what makes it a closure. |
| * **`FeedbackCell`**: Points to a cell that holds the `FeedbackVector`. |
| * **`JSDispatchHandle`**: In modern V8, this is a 32-bit index into the `JSDispatchTable`, used to find the executable code, enabling efficient tiering. |
| |
| ### `FeedbackCell` |
| The `FeedbackCell` acts as a level of indirection between the `JSFunction` and the `FeedbackVector`. |
| * **Purpose**: |
| * **Tiering Control**: It stores the `interrupt_budget`, which is decremented on function entry and backward branches. When it reaches zero, it triggers a tiering request. |
| * **Lazy Allocation**: It allows V8 to delay the allocation of the full `FeedbackVector` until the function becomes "hot". |
| * **Sharing**: In some cases, multiple closures can share the same `FeedbackCell` if they are guaranteed to have the same feedback behavior. |
| |
| ### `FeedbackVector` |
| The `FeedbackVector` holds the runtime feedback (Inline Caches) used by the optimizing compilers. |
| * **Purpose**: To collect data about the types and shapes of objects the function operates on. |
| * **Key Contents**: |
| * **Slots**: Array of slots, each corresponding to an operation in the bytecode. |
| * **`ClosureFeedbackCellArray`**: Holds `FeedbackCell`s for inner closures. |
| * **Context Dependency**: Feedback is highly dependent on the types encountered in a specific context. Sharing feedback across different contexts with different types would lead to polymorphism and deoptimizations. |
| |
| ## FeedbackVector Chaining via `CreateClosure` Slots |
| |
| The dynamic chaining happens through the feedback system, specifically to allow inner functions to have their own feedback vectors when they become hot. |
| |
| 1. **`ClosureFeedbackCellArray`**: Every `FeedbackVector` (and also uncompiled `FeedbackCell`s that are preparing for vector allocation) contains a `ClosureFeedbackCellArray`. |
| 2. **Slot per Closure**: For every `CreateClosure` bytecode in a function, there is a corresponding slot in its `ClosureFeedbackCellArray`. |
| 3. **Pre-allocated FeedbackCells**: This array is populated with `FeedbackCell`s *before* the closures are actually created. |
| 4. **Passing the Cell**: When `CreateClosure` is executed, it looks up the pre-allocated `FeedbackCell` from the outer function's `ClosureFeedbackCellArray` and links it to the new `JSFunction`. |
| |
| This creates a runtime chain: |
| `Outer JSFunction` -> `Outer FeedbackVector` -> `ClosureFeedbackCellArray` -> `Inner FeedbackCell` -> `Inner FeedbackVector` (once allocated). |
| |
| ## Modern V8 Features |
| |
| ### Sharing of `JSDispatchHandle` |
| 1. **Initialization**: Each `FeedbackCell` in `ClosureFeedbackCellArray` is allocated a `JSDispatchHandle` (initially pointing to `CompileLazy`). |
| 2. **Closure Creation**: `CreateClosure` copies the `JSDispatchHandle` from the `FeedbackCell` into the new `JSFunction`. |
| 3. **Shared Dispatch**: Closures created from the same site share the same `JSDispatchTable` entry. |
| 4. **Efficient Tiering**: Updating the code pointer in the table entry updates all sharing closures immediately. |
| |
| ### `FeedbackCell` State Transitions |
| V8 tracks the number of closures created from a site using the `Map` of the `FeedbackCell`: |
| * `NoClosuresCellMap` -> `OneClosureCellMap` -> `ManyClosuresCellMap` |
| These help decide when to allocate a `FeedbackVector` and manage specialized code. |
| |
| ## Concrete Example |
| |
| Consider the following code: |
| |
| ```javascript |
| // Top-level script |
| function outer() { |
| function inner() { |
| return 42; |
| } |
| return inner; |
| } |
| |
| outer(); // Call to outer |
| ``` |
| |
| ### Object Graph |
| |
| Here is what the object graph looks like after the top-level script has been executed and `outer()` has been called, but before `inner()` is called. |
| |
| ```dot |
| digraph G { |
| rankdir=TB; |
| newrank=true; |
| node [shape=record]; |
| edge []; |
| |
| subgraph cluster_static { |
| label="Static Structure (SFIs)"; |
| TL_SFI [label="Top-level Script (SFI)"]; |
| Outer_SFI [label="outer (SFI)"]; |
| Inner_SFI [label="inner (SFI)"]; |
| |
| TL_SFI -> Outer_SFI [label="via Bytecode's\nConstant Pool"]; |
| Outer_SFI -> Inner_SFI [label="via Bytecode's\nConstant Pool"]; |
| } |
| |
| subgraph cluster_dynamic { |
| label="Dynamic Instances (Runtime)"; |
| TL_JSF [label="Top-level Script (JSFunction)"]; |
| TL_FC [label="FeedbackCell"]; |
| CC_TL [label="CompileScript"]; |
| TL_FV [label="FeedbackVector"]; |
| Outer_FC [label="FeedbackCell\n(for outer)"]; |
| CC_Outer [label="CreateClosure\n(Outer)"]; |
| Outer_JSF [label="outer (JSFunction)"]; |
| Outer_FV [label="FeedbackVector"]; |
| Inner_FC [label="FeedbackCell\n(for inner)"]; |
| CC_Inner [label="CreateClosure\n(Inner)"]; |
| Inner_JSF [label="inner (JSFunction)"]; |
| Inner_FV [label="FeedbackVector\nor Undefined"]; |
| |
| TL_FC -> TL_JSF [style=invis]; |
| TL_FC -> CC_TL [label="consumed by", style=dashed]; |
| CC_TL -> TL_JSF [label="creates", style=dashed]; |
| |
| TL_JSF -> TL_FC; |
| TL_FC -> TL_FV; |
| TL_FV -> Outer_FC [label="contains"]; |
| |
| Outer_FC -> Outer_JSF [style=invis]; |
| Outer_FC -> CC_Outer [label="consumed by", style=dashed]; |
| CC_Outer -> Outer_JSF [label="creates", style=dashed]; |
| |
| Outer_JSF -> Outer_FC; |
| Outer_FC -> Outer_FV; |
| Outer_FV -> Inner_FC [label="contains"]; |
| |
| Inner_FC -> Inner_JSF [style=invis]; |
| Inner_FC -> CC_Inner [label="consumed by", style=dashed]; |
| CC_Inner -> Inner_JSF [label="creates", style=dashed]; |
| |
| Inner_JSF -> Inner_FC; |
| Inner_FC -> Inner_FV; |
| } |
| |
| TL_SFI -> CC_TL [label="consumed by", style=dashed]; |
| Outer_SFI -> CC_Outer [label="consumed by", style=dashed]; |
| Inner_SFI -> CC_Inner [label="consumed by", style=dashed]; |
| |
| TL_JSF -> TL_SFI; |
| Outer_JSF -> Outer_SFI; |
| Inner_JSF -> Inner_SFI; |
| |
| TL_JSF -> Outer_JSF [style=invis]; |
| Outer_JSF -> Inner_JSF [style=invis]; |
| |
| { rank="same"; TL_SFI; TL_FC; } |
| { rank="same"; Outer_SFI; Outer_FC; } |
| { rank="same"; Inner_SFI; Inner_FC; } |
| } |
| ``` |
| |
| ### Scope and Context Graph |
| |
| This graph shows how `ScopeInfo` (static) and `Context` (dynamic) are linked. |
| |
| ```dot |
| digraph G { |
| rankdir=TB; |
| newrank=true; |
| node [shape=record]; |
| edge []; |
| |
| subgraph cluster_static { |
| label="Static Structure (Scopes)"; |
| TL_SFI [label="Top-level Script (SFI)"]; |
| TL_SI [label="Top-level ScopeInfo"]; |
| Outer_SFI [label="outer (SFI)"]; |
| Outer_SI [label="outer ScopeInfo"]; |
| Inner_SFI [label="inner (SFI)"]; |
| Inner_SI [label="inner ScopeInfo"]; |
| |
| TL_SFI -> TL_SI; |
| Outer_SFI -> Outer_SI [constraint=false]; |
| Inner_SFI -> Inner_SI [constraint=false]; |
| |
| Inner_SI -> Outer_SI -> TL_SI [label="parent"]; |
| } |
| |
| subgraph cluster_dynamic { |
| label="Dynamic Instances (Contexts)"; |
| TL_JSF [label="Top-level Script (JSFunction)"]; |
| TL_Ctx [label="Top-level Context"]; |
| Outer_JSF [label="outer (JSFunction)"]; |
| Outer_Ctx [label="outer Context"]; |
| Inner_JSF [label="inner (JSFunction)"]; |
| Inner_Ctx [label="inner Context"]; |
| |
| TL_JSF -> TL_Ctx; |
| Outer_JSF -> Outer_Ctx [constraint=false]; |
| Inner_JSF -> Inner_Ctx [constraint=false]; |
| |
| Inner_Ctx -> Outer_Ctx -> TL_Ctx [label="parent"]; |
| } |
| |
| TL_Ctx -> TL_SI [constraint=false]; |
| Outer_Ctx -> Outer_SI [constraint=false]; |
| Inner_Ctx -> Inner_SI [constraint=false]; |
| |
| TL_JSF -> TL_SFI [constraint=false]; |
| Outer_JSF -> Outer_SFI [constraint=false]; |
| Inner_JSF -> Inner_SFI [constraint=false]; |
| |
| TL_SFI -> Outer_SFI -> Inner_SFI [style=invis]; |
| TL_JSF -> Outer_JSF -> Inner_JSF [style=invis]; |
| TL_SI -> Outer_SI -> Inner_SI [style=invis]; |
| TL_Ctx -> Outer_Ctx -> Inner_Ctx [style=invis]; |
| } |
| ``` |
| |
| ## Developer Guide: Tracing JSFunction Initialization |
| |
| ### Key Entry Points |
| * `JSFunction::CreateAndAttachFeedbackVector` |
| * `JSFunction::EnsureClosureFeedbackCellArray` |
| * `FastNewClosure` (Builtin) |
| |
| --- |
| |
| ## See Also |
| - [Hidden Classes and Inline Caches](hidden-classes-and-ics.md) |
| - [Scopes and ScopeInfos](scopes-and-scope-infos.md) |