MicroQuickJS: a 10 kB Embedded JavaScript Engine
MicroQuickJS: a 10 kB Embedded JavaScript Engine
MicroQuickJS (aka MQuickJS) is an open‑source JavaScript engine written in C that is specifically designed for constrained devices. It builds on the QuickJS code base but replaces the reference‑counting GC with a tracing, compacting collector and removes many of the language features that would bloat memory usage. The result is a 32‑bit runtime that needs only 10 kB of RAM and about 100 kB of ROM on an Arm Thumb‑2 target.
Why MicroQuickJS?
- Tiny footprint – The core engine is around 10 kB of RAM on a 32‑bit CPU.
- Strict and safe by design – Only a strict ES5 subset is supported; features like
withor dynamic scoping are omitted. - Tracing GC – A compacting collector reduces object overhead and eliminates fragmentation.
- No libc dependency – The engine uses its own allocator and does not call
malloc/free, making it suitable for bare‑metal firmware. - C API – Exposes the engine to native C programs while handling pointer relocation automatically.
- Bytecode export – Scripts can be compiled once and stored in ROM.
Getting Started – Build the REPL
Clone the repository:
git clone https://github.com/bellard/mquickjs.git
cd mquickjs
Compile the REPL (mqjs):
make
You should see mqjs in the repository root. A quick sanity check:
./mqjs -e "console.log('Hello MQuickJS');"
# Hello MQuickJS
Using the Memory‑limit Option
Embedded targets can set a hard RAM ceiling. The REPL accepts --memory-limit:
./mqjs --memory-limit 10k tests/mandelbrot.js
# Renders the Mandelbrot set in the terminal
For a 32‑bit ARM board you can even generate 32‑bit bytecode for a 64‑bit host:
./mqjs -o mandelbrot.bin -m32 tests/mandelbrot.js
Running pre‑compiled bytecode
The -b flag allows running bytecode directly:
./mqjs -b mandelbrot.bin
The bytecode format is stack‑based, but it internally uses an atom table so it is portable across architectures that share the same endianness.
Exploring the C API
MicroQuickJS exposes a C API extremely similar to QuickJS but adapted for the compact GC.
#include "mquickjs.h"
int main(void) {
uint8_t mem_buf[8192];
JSContext *ctx = JS_NewContext(mem_buf, sizeof(mem_buf), &js_stdlib);
if (!ctx) {
fprintf(stderr, "Cannot allocate context\n");
return 1;
}
// Run a simple script
JSValue result = JS_EvalCode(ctx, "1 + 2", "test.js", JS_EVAL_TYPE_GLOBAL);
int i = JS_ToInt32(ctx, &i, result);
printf("Result: %d\n", i);
JS_FreeContext(ctx);
return 0;
}
Key points for using the API:
- No explicit freeing – Objects are moved by GC, so you never call
JS_FreeValue. - Use
JS_PushGCRef/JS_PopGCRef– To keep references alive across allocations. - Custom libraries – Compile the standard library to ROM with
mquickjs_build.c.
Strict mode and language subset
MicroQuickJS is strict mode only. Some typical pitfalls are eliminated:
- All global variables must be declared with
var. Arraycannot have holes; writing beyond the length throws a TypeError.evalis only supported indirectly via the globalevalfunction.- No
new Number(1). Boxing is unsupported. String.prototype.toLowerCase()/toUpperCase()only handle ASCII.RegExpuses ASCII-only case folding.
These restrictions make the engine predictable and save memory.
Performance and Benchmarks
MicroQuickJS passes QuickJS’s micro‑benchmark in comparable time while using roughly half the memory on 32‑bit hardware. Additional tests show it can run the V8 Octane benchmark in strict mode with a 1–2× speed penalty but a 4× smaller memory impact.
make test
# All unit tests pass.
make microbench
# 10 kB memory usage, script execution ~120 ms on a RasPi‑Zero.
Summary
MicroQuickJS provides a compelling solution for developers who need a JavaScript runtime on tiny devices. Its 10 kB RAM footprint, strict ES5 subset, and tracing GC make it an excellent fit for firmware, IoT gateways, or any embedded system that benefits from JavaScript's flexibility without the overhead of a large engine. Whether you build directly with the REPL, embed the C API into your firmware, or ship pre‑compiled bytecode, MicroQuickJS offers a streamlined path to JavaScript on the edge.