Back to V8

Function Architecture in V8

docs/runtime/function-architecture.md

15.0.1010.5 KB
Original Source

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 NativeContexts.
  • Key Contents:
    • Bytecode: The executable bytecode generated by Ignition.
    • ScopeInfo: The lexical structure of the scope (see Scopes and ScopeInfos).
    • 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 FeedbackCells 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 FeedbackCells 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 FeedbackCells 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