Skip to main content
LibJS is Ladybird’s JavaScript engine, implementing the ECMAScript specification from the ground up. It provides a complete JavaScript runtime with lexing, parsing, bytecode compilation, and execution capabilities.

Architecture overview

LibJS consists of several key subsystems working together:

Lexer & Parser

Source code tokenization and AST generation

Bytecode compiler

AST to bytecode compilation and optimization

Virtual machine

Bytecode execution and runtime management

Garbage collector

Automatic memory management via LibGC

Core components

Lexer and tokenization

The lexer (Lexer.cpp, Lexer.h) breaks JavaScript source code into tokens:
class Lexer {
public:
    Token next();
    // Tokenizes JavaScript source into structured tokens
};
The lexer handles:
  • Keywords: function, class, const, etc.
  • Identifiers: Variable and function names
  • Literals: Strings, numbers, booleans, null, undefined
  • Operators: Arithmetic, logical, bitwise, assignment
  • Punctuation: Braces, brackets, semicolons

Parser and AST

The parser (Parser.cpp, Parser.h) transforms tokens into an Abstract Syntax Tree:
class Parser {
public:
    NonnullRefPtr<Program> parse_program();
    // Builds AST from token stream
};
The AST (AST.h, AST.cpp) represents the syntactic structure:
  • Statements: if, while, for, return, etc.
  • Expressions: Function calls, binary operations, member access
  • Declarations: Variables, functions, classes
  • Modules: Import/export statements
The parser is over 200,000 lines and handles all ECMAScript syntax including modern features like async/await, destructuring, and optional chaining.

Bytecode compilation

LibJS compiles the AST to bytecode for efficient execution: Directory structure:
LibJS/Bytecode/
  ├── Op.h              # Bytecode operations
  ├── Interpreter.cpp   # Bytecode execution
  ├── Generator.cpp     # AST to bytecode compilation
  └── BasicBlock.cpp    # Control flow structures
The bytecode system includes:
  • Operations: Load, store, arithmetic, jumps, calls
  • Registers: Virtual registers for values
  • Basic blocks: Control flow graph nodes
  • Optimizations: Constant folding, dead code elimination
enum class OpType {
    Load,
    Store,
    Add,
    Call,
    Jump,
    // ... many more operations
};

Virtual machine

The VM (Runtime/VM.h, Runtime/VM.cpp) manages JavaScript execution:
class VM : public RefCounted<VM> {
public:
    static NonnullRefPtr<VM> create();
    
    GC::Heap& heap();
    Bytecode::Interpreter& bytecode_interpreter();
    
    // Execution context management
    // Promise job queue
    // Module loading
};

Execution contexts

Call stack, variable scopes, and this binding

Well-known symbols

Built-in symbols like Symbol.iterator

String caching

Optimization for frequently used strings

Module loading

ESM module resolution and execution

Runtime objects

The Runtime/ directory contains all built-in JavaScript objects:
Runtime/
  ├── Object.cpp/.h           # Base Object
  ├── Array.cpp/.h            # Array and methods
  ├── Function.cpp/.h         # Function objects
  ├── Promise.cpp/.h          # Promises and async
  ├── Map.cpp/.h              # Map collection
  ├── Set.cpp/.h              # Set collection
  ├── RegExpObject.cpp/.h     # Regular expressions
  ├── Date.cpp/.h             # Date and time
  └── ... (100+ built-in types)
Each built-in follows a pattern:
  • Constructor: Creates instances (e.g., ArrayConstructor)
  • Prototype: Methods available on instances (e.g., ArrayPrototype)
  • Object: Internal representation (e.g., Array)
Looking for how Array.prototype.map() works? Check Runtime/ArrayPrototype.cpp!

Garbage collection

LibJS uses LibGC for automatic memory management:
class Object : public GC::Cell {
    GC_CELL(Object, GC::Cell);
    // All JS objects are garbage collected
};
Key concepts:
  • GC::Cell: Base class for all GC-managed objects
  • GC::Ref<T>: Non-null reference to GC object
  • GC::Ptr<T>: Nullable pointer to GC object
  • Root vectors: Temporary roots for stack values
All heap-allocated JavaScript objects must inherit from GC::Cell and be allocated through the GC heap. Direct new allocation will cause memory issues.

ECMAScript compliance

LibJS implements modern ECMAScript features:
FeatureStatusFiles
ES6 ClassesRuntime/ECMAScriptFunctionObject.cpp
Arrow functionsAST.h, Parser.cpp
DestructuringParser.cpp, AST.h
Template literalsLexer.cpp, Parser.cpp
PromisesRuntime/Promise.cpp
async/awaitBytecode/, AST.cpp
Modules (ESM)Module.cpp, SourceTextModule.cpp
GeneratorsRuntime/GeneratorObject.cpp
ProxiesRuntime/ProxyObject.cpp
SymbolsRuntime/Symbol.cpp

Completions and error handling

LibJS uses the “completion” system from the ECMAScript spec:
enum class CompletionType {
    Normal,
    Break,
    Continue,
    Return,
    Throw
};

template<typename T>
using ThrowCompletionOr = Completion;
This allows propagating:
  • Normal values: Successful execution
  • Exceptions: Thrown errors
  • Control flow: break, continue, return
// Example: Propagating exceptions
ThrowCompletionOr<Value> do_something(VM& vm) {
    auto result = TRY(call_function(vm));
    return result;
}
The TRY() macro automatically propagates throw completions, similar to Rust’s ? operator or exception handling in other languages.

Module system

LibJS implements ECMAScript modules: Module types:
  • SourceTextModule: Regular JavaScript modules (.js, .mjs)
  • SyntheticModule: Programmatically created modules
  • CyclicModule: Base for modules with cyclic dependencies
class SourceTextModule : public CyclicModule {
    // import/export resolution
    // Module evaluation
    // Dependency tracking
};
Module loading phases:
  1. Parse: Tokenize and parse module source
  2. Link: Resolve imports and exports
  3. Evaluate: Execute module code
  4. Cache: Store for future imports

Console API

The Console class (Console.cpp, Console.h) implements the console API:
console.log("Hello");
console.error("Error!");
console.trace();
console.time("operation");
console.timeEnd("operation");
Implemented methods:
  • log(), info(), warn(), error()
  • trace(), assert()
  • time(), timeEnd(), timeLog()
  • group(), groupEnd(), groupCollapsed()
  • clear(), count(), countReset()

Scope analysis

The scope collector (ScopeCollector.cpp, ScopeCollector.h) analyzes variable scopes:
  • Lexical scoping: Block-level scopes for let and const
  • Function scoping: Function-level scopes for var
  • Closure analysis: Captures and closures
  • Hoisting: Variable and function hoisting
class ScopeCollector {
    // Analyzes AST to build scope trees
    // Determines variable binding locations
    // Identifies closures and captures
};

Integration with LibWeb

LibJS is deeply integrated with LibWeb:
// LibWeb objects inherit from JS::Cell
class HTMLElement : public JS::Cell {
    GC_CELL(HTMLElement, JS::Cell);
    // Can be used directly in JavaScript
};
Integration points:
  • Realms: Each document has a JS realm
  • Global objects: window, document, etc.
  • Event handlers: JavaScript event callbacks
  • Promises: For async web APIs
  • WebIDL bindings: Generated glue code

Development tools

AST dumping

// ASTDump.cpp - Pretty-print AST structure
ast->dump(indent_level);

Pretty printing

// Print.cpp - Convert AST back to readable JS
ast->print_to_string();

Syntax highlighting

// SyntaxHighlighter.cpp - Colorize JS code
auto highlighted = highlight_syntax(source_code);

Rust integration

LibJS includes experimental Rust integration:
LibJS/Rust/
  └── Integration code for Rust components

RustIntegration.cpp/.h
  └── FFI bindings between C++ and Rust
The Rust integration allows certain components to be implemented in Rust while maintaining C++ compatibility.

Performance optimizations

LibJS includes several performance optimizations:
  • String interning: Reuse identical strings
  • Inline caching: Cache property lookups
  • Bytecode compilation: Faster than AST interpretation
  • JIT preparation: Architecture supports future JIT compilation
// String cache for frequently used strings
auto& string_cache() { return m_string_cache; }

// Single ASCII character strings are pre-allocated
PrimitiveString& single_ascii_character_string(u8 character);

LibWeb

Web rendering engine using LibJS

LibWasm

WebAssembly implementation

LibGC

Garbage collection infrastructure

Build docs developers (and LLMs) love