7 puntos por GN⁺ 2025-12-24 | 2 comentarios | Compartir por WhatsApp
  • MicroQuickJS (MQuickJS) es un motor de JavaScript ultraligero diseñado para sistemas embebidos, capaz de ejecutarse con apenas 10 kB de RAM y 100 kB de ROM
  • Mantiene una velocidad similar a QuickJS mientras reduce el uso de memoria mediante un recolector de basura por trazado y almacenamiento de cadenas en UTF-8
  • El lenguaje compatible es un subconjunto limitado de JavaScript cercano a ES5, y solo permite modo estricto (strict mode), que prohíbe sintaxis propensa a errores
  • La herramienta REPL mqjs permite ejecutar scripts, guardar bytecode y configurar límites de memoria; el bytecode generado puede ejecutarse directamente desde ROM
  • Todo el motor y la biblioteca estándar residen en ROM, logrando inicialización rápida y bajo consumo de memoria, y mejorando la eficiencia de ejecución de JavaScript en entornos embebidos

Introducción

  • MicroQuickJS (MQuickJS) es un motor de JavaScript orientado a sistemas embebidos, que funciona con 10 kB de RAM y 100 kB de ROM (incluyendo código ARM Thumb-2)
    • Su velocidad es similar a la de QuickJS
  • Solo admite un subconjunto cercano a ES5 y funciona exclusivamente en modo estricto (strict mode), que prohíbe sintaxis ineficiente o propensa a errores
  • Comparte parte del código con QuickJS, pero su estructura interna fue diseñada de forma completamente distinta para ahorrar memoria
    • Usa recolector de basura por trazado, sin uso de la pila de CPU y almacenamiento de cadenas en UTF-8

REPL

  • El comando REPL es mqjs y admite ejecución de scripts, evaluación, modo interactivo, configuración de límites de memoria y guardado de bytecode
    • Ejemplo: ./mqjs --memory-limit 10k tests/mandelbrot.js
  • Con la opción -o se puede guardar el bytecode compilado en un archivo
    • El bytecode guardado puede ejecutarse con ./mqjs mandelbrot.bin
  • El bytecode varía según el endianness del CPU y la longitud de palabra (32/64 bits); con la opción -m32 se puede generar bytecode para 32 bits
  • La opción --no-column permite eliminar los números de columna de la información de depuración

Modo estricto

  • Solo se permite strict mode; no se puede usar la palabra clave with, y las variables globales deben declararse obligatoriamente con var
  • No se permiten huecos (hole) en arreglos
    • Ejemplo: a[10] = 2 produce un TypeError
    • Si se necesita un arreglo con huecos, debe usarse un objeto normal
  • Solo se admite eval global, sin acceso a variables locales
  • No se admite value boxing (new Number(1), etc.)

Subconjunto de JavaScript

  • Basado en strict mode, con enfoque en la compatibilidad con ES5
  • El objeto Array no tiene huecos, y el acceso a índices fuera de rango genera error
  • for in recorre solo las propiedades propias del objeto, y for of solo es compatible con arreglos
  • El objeto global existe, pero no admite getter/setter, y las propiedades creadas directamente no se exponen como variables globales
  • Las expresiones regulares (Regexp) solo distinguen mayúsculas y minúsculas en ASCII, y /./ hace coincidencia por punto de código Unicode en lugar de UTF-16
  • Las funciones de cadena solo procesan ASCII (toLowerCase, toUpperCase)
  • Date solo admite Date.now()
  • Funciones adicionales compatibles:
    • for of, Typed arrays, literales de cadena \u{hex}
    • Funciones de Math: imul, clz32, fround, trunc, log2, log10
    • Operador exponencial, flags de regexp (s, y, u), funciones de cadena (replaceAll, trimStart, trimEnd), globalThis

API de C

  • Dependencia mínima de la biblioteca estándar de C; no usa malloc, free ni printf
  • Se debe proporcionar directamente un búfer de memoria, y el motor solo asigna memoria dentro de ese búfer
    • Ejemplo: ctx = JS_NewContext(mem_buf, sizeof(mem_buf), &js_stdlib)
  • Debido al método de recolección de basura, no es necesario llamar a JS_FreeValue()
  • Como la dirección de los objetos puede cambiar en cada asignación, se recomienda usar punteros a JSValue
    • JS_PushGCRef() / JS_PopGCRef() permiten una gestión segura de referencias
  • La biblioteca estándar se compila como estructuras en C que pueden almacenarse en ROM, logrando inicialización rápida y bajo uso de RAM
  • La ejecución de bytecode puede hacerse desde ROM, reubicándolo con JS_RelocateBytecode() y luego ejecutándolo con JS_LoadBytecode() y JS_Run()
  • Incluye una biblioteca matemática (libm.c) y un emulador de punto flotante integrados

Estructura interna y comparación con QuickJS

  • Recolección de basura: usa GC por trazado y compactación en lugar de conteo de referencias
    • Evita la fragmentación de memoria y reduce el tamaño de los objetos
  • Representación de valores: diseñada según el tamaño de palabra del CPU (32/64 bits)
    • Puede almacenar enteros de 31 bits, puntos de código Unicode, números de punto flotante y punteros a bloques de memoria
  • Las cadenas se almacenan en UTF-8, de forma más eficiente que el esquema de arreglos de 8/16 bits de QuickJS
  • Las funciones en C pueden almacenarse como un solo valor, pero no admiten propiedades adicionales
  • La biblioteca estándar reside en ROM y minimiza los objetos en RAM, lo que permite inicialización rápida del motor
  • El bytecode está basado en pila y se maneja como solo lectura mediante una tabla de referencias indirectas
    • Usa código Golomb para comprimir números de línea y columna
  • El compilador es similar al de QuickJS, pero usa un parser no recursivo para limitar el uso de la pila de C
    • Genera bytecode en una sola pasada, sin árbol de análisis

Pruebas y benchmarks

  • Pruebas básicas: make test
  • Microbenchmarks de QuickJS: make microbench
  • El benchmark Octane (versión modificada para modo estricto) puede descargarse por separado
    • Ejecución: make octane

Licencia

  • Distribuido bajo la licencia MIT
  • El copyright del código fuente pertenece a Fabrice Bellard y Charlie Gordon

2 comentarios

 
xguru 2025-12-24

Para una introducción a Fabrice Bellard, consulten lo que escribí antes en un comentario. Este tipo es de verdad una bestia sorprendente y constante.
https://news.hada.io/comment?id=51

 
GN⁺ 2025-12-24
Comentarios en Hacker News
  • Si algo así hubiera existido en 2010, creo que el lenguaje de scripting de Redis habría sido JavaScript y no Lua
    Lua no se eligió por razones del lenguaje, sino por limitaciones de implementación: era pequeño, rápido y basado en ANSI C
    Algunas ideas de Lua están bien, pero personalmente sentía que alejarse de la sintaxis de la familia Algol era innecesario
    La confusión que surge al aprender nuevas abstracciones, como en SmallTalk o FORTH, vale la pena, pero no creo que los cambios de Lua tuvieran una justificación equivalente

    • La sintaxis de Lua no me encanta, pero creo que las razones por las que los desarrolladores lo eligieron son perfectamente entendibles
      Lua es el único lenguaje ligero que soporta tail call optimization (TCO), y gracias a eso puedes escribir programas recursivos sin usar bucles
      JavaScript no tiene esa optimización, así que no puede hacerse de la misma manera
      Lua también es especialmente adecuado para escribir compiladores, porque abundan las estructuras recursivas
      Puede que JS encaje mejor para scripting en Redis, pero es una pena menospreciar a Lua
    • Considerando que Lua apareció por primera vez en 1993, su sintaxis era bastante tradicional para la época
      En Brasil se usaban más Pascal y Ada que C, así que viene de esa influencia
      Ruby y Perl también salieron por esos años, pero intentaron cambios sintácticos mucho más radicales
    • Al principio iba a comentar que aprendí Lua fácilmente a los 13 años, pero me detuve al darme cuenta de que quien escribió el comentario era el propio antirez, y me sorprendió bastante
    • Esto no resolvería el problema de la sintaxis, pero el concepto de “language skins” me parece interesante
      Casi no ha habido intentos de intercambiar tokens como then/end en lugar de {}, manteniendo separados el parser y el lexer
      Discusiones relacionadas: hilo de HN, debate en Reddit
    • Me pregunto si alguna vez se consideró Tcl como lenguaje de scripting para Redis, ya que fue el lenguaje embebido original
  • Este motor restringe JS de la forma en que yo quería cuando trabajaba en JSC
    En la web esas limitaciones son imposibles por la compatibilidad, pero en entornos embebidos ese tipo de restricciones incluso puede ser un diseño placentero

    • Ya tenemos motores de JS sin esas restricciones
    • Me pregunto qué pasó con el trabajo de multithreading que se hacía en JSC. Quisiera saber si se detuvo al salir de Apple o si el código sigue existiendo
  • Hice un playground para ejecutar MicroQuickJS directamente en el navegador
    Versión WebAssembly de MicroQuickJS
    Como referencia, también está la versión original de QuickJS
    QuickJS pesa 2.28MB y MicroQuickJS 303KB, así que es mucho más liviano

    • Parece que el build incluye información como nombres y por eso creció tanto
      Si agregas emcc -O3 o --closure 1, probablemente podría reducirse más
      QuickJS ya parece estar optimizado, y donde todavía habría margen de mejora es en MicroQuickJS
  • Como dijo Jeff Atwood en su famosa frase, “cualquier aplicación que pueda escribirse en JavaScript, eventualmente se escribirá en JavaScript
    Ahora parece que eso también aplica a los sistemas embebidos
    Wiki de Jeff Atwood

  • Es una lástima que se haya subido sin historial de commits
    Me habría gustado ver qué tan rápido alguien de ese nivel puede terminar un proyecto
    De todos modos, como está basado en QuickJS, la comparación probablemente no tendría mucho sentido

    • Por la expresión “public repository of…”, es posible que el historial real del trabajo esté en un repositorio privado
    • O quizá simplemente lo terminó de una sola vez
  • Me pregunto si esta podría ser la forma más ligera de resolver el desafío JS de YouTube en yt-dlp
    Ver la documentación EJS de yt-dlp
    QuickJS ya está soportado

    • Es poco probable, porque solo soporta una implementación parcial a nivel ES5
      Los acertijos JS de YouTube son tan complejos que hasta un emulador de JS hecho en Python terminó dándose por vencido
    • Como solo implementa ES5, lo veo poco realista en la práctica
  • No sé mucho de sistemas embebidos, pero me pregunto si algo así podría permitir programar ESP32 o Arduino en JavaScript
    Algo al estilo de MicroPython

    • Ya existen proyectos parecidos
    • El motor XS de Moddable/Kinoma soporta ES6 en adelante
      MicroQuickJS solo implementa parte de ES5 y tampoco ofrece bindings del entorno
    • Antes existía una placa de programación en JS llamada Tessel
      Ejecutaba el código JS convirtiéndolo a bytecode de una VM de Lua, y era un enfoque bastante ingenioso
      Hace poco reescribí en Rust aquella vieja CLI de Node 0.8, pero al final el hardware volvió al cajón
    • La clave es que tiene una arquitectura sin malloc(). Eso es lo que abre posibilidades
  • El timing realmente importa. Cuando lo publicaron anoche no tuvo ninguna reacción

    • Probablemente solo fue cuestión de suerte
    • Otra persona también lo intentó y tampoco obtuvo respuesta
      Hay quien lo vuelve a publicar en la mañana de EE. UU. o lo repostea periódicamente como estrategia
  • Fabrice Bellard es uno de los programadores más productivos y versátiles que existen hoy
    Obras destacadas: FFmpeg, QEMU, JSLinux, TCC, QuickJS
    Es una figura legendaria

    • Aunque es tan admirado, poca gente parece interesarse por su forma de desarrollar
      Impresiona su manera de construir programas completos con dependencias y herramientas mínimas
    • Ya me dan ganas de pensar que no es una sola persona, sino el nombre en clave de un grupo de hackers expertos
      Porque si fuera una persona real, tendría que dormir
    • También desarrolló por su cuenta un motor de inferencia para LLM, que mantiene desde la época de GPT-2
      ts_server, TextSynth
    • Algo interesante es que la mayoría de los programas que crea no se enfocan en interfaces gráficas centradas en el usuario
      Parece preferir estructuras donde el usuario ajusta parámetros y luego el programa se ejecuta de forma autosuficiente
    • También ganó 3 veces el International Obfuscated C Code Contest (IOCCC)
      Lista de ganadores del IOCCC
  • Impresiona eso de que “se puede compilar y ejecutar JS incluso con 10kB de RAM”
    Llega justo en una época en la que la RAM se está encareciendo
    Me pregunto si se podría meter en Chromium o Electron

    • Sería difícil por la compatibilidad web, pero de todos modos el ahorro de memoria en Chromium probablemente no sería tan grande