MicroQuickJS: un motor JavaScript embebido de 10 kB
MicroQuickJS: un motor JavaScript embebido de 10 kB
MicroQuickJS (también conocido como MQuickJS) es un motor JavaScript de código abierto escrito en C, diseñado específicamente para dispositivos con recursos limitados. Se basa en la base de código de QuickJS, pero reemplaza el GC de conteo de referencias por un recolector con trazado y compactación, y elimina muchas características del lenguaje que incrementarían el uso de memoria. El resultado es un entorno de ejecución de 32 bits que necesita solo 10 kB de RAM y alrededor de 100 kB de ROM en un objetivo Arm Thumb‑2.
¿Por qué MicroQuickJS?
- Huella muy reducida – El núcleo del motor ocupa alrededor de 10 kB de RAM en una CPU de 32 bits.
- Estricto y seguro por diseño – Solo se admite un subconjunto estricto ES5; las características como
witho la asignación dinámica son omitidas. - GC con trazado – Un recolector con compactación reduce la sobrecarga de objetos y elimina la fragmentación.
- Sin dependencia de libc – El motor utiliza su propio asignador y no llama a
malloc/free, lo que lo hace adecuado para firmware sin sistema operativo. - API en C – Expone el motor a programas nativos en C, gestionando la reubicación de punteros automáticamente.
- Exportación de bytecode – Los scripts pueden compilarse una vez y almacenarse en ROM.
Empezando – Construir el 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
Usando la opción de límite de memoria
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
Ejecutando bytecode precompilado
The -b flag allows running bytecode directly:
./mqjs -b mandelbrot.bin
El formato de bytecode es basado en pila, pero internamente utiliza una tabla de átomos, por lo que es portátil entre arquitecturas que comparten el mismo orden de bytes.
Explorando la API en C
MicroQuickJS expone una API en C extremadamente similar a QuickJS pero adaptada para el GC compacto.
#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;
}
Puntos clave para usar la API:
- Sin liberación explícita – Los objetos son movidos por el GC, por lo que nunca se llama a
JS_FreeValue. - Use
JS_PushGCRef/JS_PopGCRef– Para mantener referencias vivas entre asignaciones. - Librerías personalizadas – Compile la biblioteca estándar a ROM con
mquickjs_build.c.
Modo estricto y subconjunto de lenguaje
MicroQuickJS es solo modo estricto. Algunas trampas típicas son eliminadas:
- Todas las variables globales deben declararse con
var. Arrayno puede tener huecos; escribir más allá de su longitud provoca unTypeError.evalsolo se admite indirectamente a través de la función globaleval.- No
new Number(1). El envoltorio no es soportado. String.prototype.toLowerCase()/toUpperCase()solo manejan ASCII.RegExpusa sólo folding en ASCII.
Estas restricciones hacen que el motor sea predecible y ahorran memoria.
Rendimiento y Benchmarks
MicroQuickJS pasa la microprueba de QuickJS en un tiempo comparable mientras usa aproximadamente la mitad de memoria en hardware de 32 bits. Pruebas adicionales muestran que puede ejecutar el benchmark V8 Octane en modo estricto con una penalización de velocidad de 1–2× pero con un impacto de memoria 4× menor.
make test
# All unit tests pass.
make microbench
# 10 kB memory usage, script execution ~120 ms on a RasPi‑Zero.
Resumen
MicroQuickJS ofrece una solución atractiva para los desarrolladores que necesitan un tiempo de ejecución de JavaScript en dispositivos pequeños. Su huella de 10 kB de RAM, el subconjunto estricto ES5 y el GC con trazado lo convierten en una excelente opción para firmware, pasarelas IoT o cualquier sistema embebido que se beneficie de la flexibilidad de JavaScript sin la sobrecarga de un motor grande. Ya sea que construya directamente con el REPL, incruste la API en C en su firmware o distribuye bytecode precompilado, MicroQuickJS ofrece un camino simplificado hacia JavaScript en el borde.