Internal Architecture
This document describes the internal architecture and design decisions of the QuickJS-ng JavaScript engine.Compiler Architecture
Direct Bytecode Generation
The QuickJS compiler generates bytecode directly with no intermediate representation such as a parse tree. This design provides several advantages:- Very fast compilation
- Lower memory usage during compilation
- Simpler compiler implementation
Stack-Based Bytecode
QuickJS uses a stack-based bytecode format because it:- Is simple to implement and understand
- Generates compact code
- Maps well to common CPU architectures
Debug Information
A separate compressed line number table is maintained for debug information, keeping bytecode compact while preserving debugging capabilities.Optimizations
- Closure variables are optimized and accessed almost as fast as local variables
- Direct
evalin strict mode is specially optimized - Multiple bytecode optimization passes improve performance
Runtime Architecture
JSValue Representation
JSValue is the core type representing any JavaScript value (primitives or objects).32-bit Architecture
- Uses NaN boxing to store 64-bit floating-point numbers efficiently
- Representation is optimized for fast testing of 32-bit integers and reference-counted values
- JSValue fits in two CPU registers for efficient function returns
64-bit Architecture
- JSValue is 128-bit (no NaN boxing)
- Rationale: Memory usage is less critical on 64-bit systems
- Still fits in two CPU registers for efficient returns
Strings
Strings are stored as either:- 8-bit character array (for ASCII/Latin-1)
- 16-bit character array (for Unicode)
- Random character access is always fast (O(1))
- Memory efficient for ASCII-heavy content
- Full Unicode support when needed
C API Integration
The C API provides functions to convert JavaScript strings to UTF-8 encoded C strings. The most common case (ASCII-only strings) involves no copying, improving performance.Objects
Shape Sharing
Object shapes (also called hidden classes) are shared between objects. A shape includes:- Object prototype
- Property names
- Property flags
Array Optimizations
- Arrays with no holes (except at the end) are optimized
- Dense arrays use efficient storage
- TypedArray accesses are specially optimized
Atoms
Object property names and frequently used strings are stored as Atoms (unique interned strings). Representation:- Atoms are 32-bit integers
- Fast comparison (integer comparison instead of string comparison)
- Memory savings through deduplication
Numbers
Numbers are represented as either:- 32-bit signed integers (fast path)
- 64-bit IEEE-754 floating-point values (fallback)
Garbage Collection
Reference Counting
QuickJS uses reference counting as the primary garbage collection mechanism:- Objects are freed automatically and deterministically
- No pause times for garbage collection
- Predictable memory usage
Cycle Collection
A separate cycle removal pass runs when allocated memory exceeds a threshold:- Detects and breaks reference cycles
- Only uses reference counts and object content
- No explicit GC roots need to be manipulated in C code
Function Calls
The engine is optimized for fast function calls:- The system stack holds JavaScript parameters and local variables
- No separate JavaScript call stack is maintained
- Direct mapping to native calling conventions
- Minimal overhead for function calls
Regular Expressions
Custom RegExp Engine
QuickJS includes a custom regular expression engine that is:- Small (~15 KiB x86 code, excluding Unicode library)
- Efficient with good performance characteristics
- Feature-complete supporting all ES2020+ features including Unicode properties
Direct Bytecode Generation
Like the JavaScript compiler, the RegExp engine:- Directly generates bytecode
- No parse tree is created
- Fast compilation
Backtracking Implementation
- Uses explicit stack for backtracking
- No recursion on the system stack (prevents stack overflow)
- Simple quantifiers are specially optimized to avoid recursion
- Infinite recursions from quantifiers with empty terms are prevented
Unicode Support
Custom Unicode Library
QuickJS includes a custom Unicode library to avoid dependency on large external libraries like ICU. Size: ~45 KiB (x86 code) Features:- Case conversion
- Unicode normalization
- Unicode script queries
- Unicode general category queries
- All Unicode binary properties
- All Unicode tables are compressed
- Reasonable access speed maintained
- Much smaller than alternatives
BigInt Support
libbf Library
BigInt is implemented using the libbf library by Fabrice Bellard. Size: ~90 KiB (x86 code) Features:- Arbitrary precision integer arithmetic
- IEEE 754 floating-point operations with arbitrary precision
- Transcendental functions with exact rounding
- Full BigInt specification support
Code Organization
Core Files
quickjs.c- Main engine implementationquickjs.h- Public C APIquickjs-libc.c- Standard library (std, os modules)quickjs-libc.h- Standard library APIlibbf.c- BigInt/BigFloat librarylibregexp.c- Regular expression enginelibunicode.c- Unicode support library
Tools
qjs.c- Command-line interpreterqjsc.c- JavaScript-to-C compilerrun-test262.c- test262 test runnerapi-test.c- C API test suite
Build System
CMakeLists.txt- Primary build systemMakefile- Helper Makefile (wraps CMake)meson.build- Alternative Meson build support
Generated Code
gen/repl.c- REPL implementation (generated fromrepl.js)gen/standalone.c- Standalone runtime (generated fromstandalone.js)- Various built-in modules (generated from JS sources)
Build Configuration
CMake Options
CMAKE_BUILD_TYPE- Debug, Release, RelWithDebInfo, MinSizeRelBUILD_SHARED_LIBS- Build as shared libraryQJS_BUILD_EXAMPLES- Build example programsQJS_BUILD_LIBC- Include std/os modulesQJS_ENABLE_ASAN- Enable AddressSanitizerQJS_ENABLE_UBSAN- Enable UndefinedBehaviorSanitizerQJS_ENABLE_MSAN- Enable MemorySanitizerQJS_BUILD_WERROR- Treat warnings as errorsQJS_DISABLE_PARSER- Build without parser (bytecode-only)QJS_WASI_REACTOR- Build as WASI reactor module
Feature Flags
CONFIG_BIGNUM- Enable BigInt/BigDecimal supportJS_NAN_BOXING- Enable NaN boxing (32-bit only)JS_CHECK_JSVALUE- Enable JSValue consistency checksCONFIG_ATOMICS- Enable SharedArrayBuffer and Atomics
Memory Management
Allocation Strategy
- Reference counting for deterministic cleanup
- Cycle detection for circular references
- Custom allocator support via
JS_NewRuntime2() - Optional mimalloc integration for improved performance
Memory Limits
- Configurable memory limits per runtime
- Stack size limits checked at compile time
- Malloc limits to prevent OOM
Performance Characteristics
Strengths
- Fast compilation (direct bytecode generation)
- Fast function calls (system stack usage)
- Efficient integer operations (32-bit fast path)
- Low memory overhead (shape sharing, atoms, reference counting)
- Predictable memory usage (deterministic GC)
Trade-offs
- Reference counting overhead on assignments
- Cycle detection pass when memory grows
- 128-bit JSValue on 64-bit platforms uses more memory than NaN boxing
Design Philosophy
QuickJS-ng follows these principles:- Simplicity - Avoid complex intermediate representations
- Performance - Optimize common cases (integers, function calls)
- Compactness - Minimize code size and memory usage
- Compliance - Support the full ECMAScript specification
- Embeddability - Easy to integrate into other applications
- Portability - Work across platforms and architectures