V8pedia

Bytecode generation

The bridge between the frontend and execution is the BytecodeGenerator: it walks the AST and emits Ignition bytecode. Along the way it allocates the feedback slots that inline caches will fill, and the result is packaged behind a SharedFunctionInfo. This page connects parsing to running.

::: info Ubiquitous language SFI (SharedFunctionInfo): the code-agnostic description of a function, shared by all its closures. BytecodeArray: the emitted bytecode for a function. Feedback slot: a reserved slot for one operation's runtime feedback. :::

SharedFunctionInfo

Every function literal has exactly one SFI, shared across all closures created from it. It holds the function's immutable identity (name, source span, parameter count) and a polymorphic function_data field that swings between states as the function is compiled, flushed, and recompiled:

// SharedFunctionInfo describes the JSFunction information that can be
// shared by multiple instances of the function.
V8_OBJECT class SharedFunctionInfo : public HeapObject {
  inline Tagged<Object> untrusted_function_data() const;

};

src/objects/shared-function-info.h#L260-L279

function_data can be a BytecodeArray (compiled), UncompiledData with PreparseData (lazy, not yet parsed), baseline Code, and more. Note the warning around is_compiled():

// Note: with bytecode flushing, any GC after this call is made could cause the
// function to become uncompiled. … use IsCompiledScope instead.
inline bool is_compiled() const;

src/objects/shared-function-info.h#L387-L391

::: details Bytecode flushing Under memory pressure, V8 can discard the bytecode of a function that hasn't run recently, reverting its SFI to uncompiled; the bytecode is regenerated on next call. This is why holding an IsCompiledScope matters when you need the bytecode to stay put. It's a memory/latency trade: spend a recompile to reclaim RAM. :::

Compilation: a two-phase job

The central Compiler hub dispatches all compilation. Producing bytecode uses an UnoptimizedCompilationJob split into two phases so the heavy work can run off the main thread:

// 1) ExecuteJob:   Runs concurrently. No heap allocation or handle derefs.
// 2) FinalizeJob:  Runs on main thread. No dependency changes.
class UnoptimizedCompilationJob : public CompilationJob {
  V8_WARN_UNUSED_RESULT Status ExecuteJob();
  V8_WARN_UNUSED_RESULT Status FinalizeJob(
      DirectHandle<SharedFunctionInfo>, Isolate*);
};

src/codegen/compiler.h#L327-L395

Parsing and bytecode generation (ExecuteJob) touch no heap and can run on a background thread; only the final installation into the SFI (FinalizeJob) is serialized on the main thread. This keeps main-thread time — the latency users feel — minimal.

Walking the AST

The generator is an AST visitor that emits bytecode into a builder:

class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
  void GenerateBytecode(uintptr_t stack_limit);
  template <typename IsolateT>
  Handle<BytecodeArray> FinalizeBytecode(IsolateT*, Handle<Script>);
};

src/interpreter/bytecode-generator.h#L41-L79

Helper builders centralize how each operation is emitted — including allocating its feedback slot:

void BuildLoadNamedProperty(const Expression* object_expr, Register object,
                            const AstRawString* name);
void BuildLoadKeyedProperty(Register object, FeedbackSlot slot);

src/interpreter/bytecode-generator.h#L323-L330

This is where the FeedbackVector layout is fixed: the generator decides, deterministically, which operations get feedback slots and in what order. That determinism is essential — the interpreter, baseline code, and the optimizers all index the same slots, so they must agree on the layout.

::: details C++ aside: visitor + scopes The generator uses the visitor pattern (double dispatch over AST node types) and a stack of scoped ControlScope helpers to track loops and try/finally, so break/continue/return resolve to the right targets even when deeply nested. RAII (constructor pushes, destructor pops) keeps that stack correct under early exits — a very common V8 idiom. :::

The handoff

After FinalizeBytecode, the SFI holds a BytecodeArray with feedback metadata, and the function is ready to run in Ignition. From there, feedback accumulates and the tiering machinery takes over.

See also