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:
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:
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:
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

Virtual machine

The VM (Runtime/VM.h, Runtime/VM.cpp) manages JavaScript execution:

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:
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:
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:

Completions and error handling

LibJS uses the “completion” system from the ECMAScript spec:
This allows propagating:
  • Normal values: Successful execution
  • Exceptions: Thrown errors
  • Control flow: break, continue, return
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
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:
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

Integration with LibWeb

LibJS is deeply integrated with LibWeb:
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

Pretty printing

Syntax highlighting

Rust integration

LibJS includes experimental Rust integration:
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

LibWeb

Web rendering engine using LibJS

LibWasm

WebAssembly implementation

LibGC

Garbage collection infrastructure