MicroQuickJS : un moteur JavaScript embarqué de 10 kB

MicroQuickJS : un moteur JavaScript embarqué de 10 kB

MicroQuickJS (également connu sous le nom MQuickJS) est un moteur JavaScript open‑source écrit en C, spécialement conçu pour les appareils contraints. Il s’appuie sur la base de code QuickJS mais remplace le ramasse-miettes à comptage de références par un collecteur de traçage compact et supprime de nombreuses fonctionnalités du langage qui gonfleraient l’utilisation de mémoire. Le résultat est un runtime 32‑bits qui nécessite seulement 10 kB de RAM et environ 100 kB de ROM sur une cible Arm Thumb‑2.

Pourquoi MicroQuickJS ?

  1. Empreinte minuscule – Le moteur central fait environ 10 kB de RAM sur un CPU 32‑bits.
  2. Strict et sûr par conception – Seul un sous‑ensemble strict ES5 est pris en charge ; les fonctionnalités comme with ou la portée dynamique sont omises.
  3. Ramasse-miettes de traçage – Un collecteur compact réduit la surcharge des objets et élimine la fragmentation.
  4. Pas de dépendance à libc – Le moteur utilise son propre allocateur et n’appelle pas malloc/free, ce qui le rend adapté aux firmwares bare‑metal.
  5. API C – Expose le moteur aux programmes C natifs tout en gérant automatiquement la relocalisation des pointeurs.
  6. Export de bytecode – Les scripts peuvent être compilés une fois et stockés en ROM.

Premiers pas – Construire le REPL

Clonez le dépôt :

git clone https://github.com/bellard/mquickjs.git
cd mquickjs

Compilez le REPL (mqjs) :

make

Vous devriez voir mqjs à la racine du dépôt. Vérification rapide :

./mqjs -e "console.log('Hello MQuickJS');"
# Hello MQuickJS

Utiliser l’option de limite de mémoire

Les cibles embarquées peuvent fixer un plafond de RAM. Le REPL accepte l'option --memory-limit :

./mqjs --memory-limit 10k tests/mandelbrot.js
# Renders the Mandelbrot set in the terminal

Sur une carte ARM 32‑bits vous pouvez même générer un bytecode 32‑bits pour un hôte 64‑bits :

./mqjs -o mandelbrot.bin -m32 tests/mandelbrot.js

Exécution de bytecode pré‑compilé

Le drapeau -b permet d'exécuter directement le bytecode :

./mqjs -b mandelbrot.bin

Le format du bytecode est basé sur la pile, mais il utilise une table des atomes en interne, ce qui le rend portable entre les architectures partageant le même endianness.

Exploration de l'API C

MicroQuickJS expose une API C très similaire à celle de QuickJS mais adaptée au ramasse-miettes compact.

#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;
}

Points clés pour l’utilisation de l’API :

  • Aucune libération explicite – Les objets sont déplacés par le GC, donc vous n’appelez jamais JS_FreeValue.
  • Utilisez JS_PushGCRef / JS_PopGCRef – Pour garder les références vivantes entre allocations.
  • Bibliothèques personnalisées – Compilez la bibliothèque standard en ROM avec mquickjs_build.c.

Mode strict et sous‑ensemble du langage

MicroQuickJS est strict mode seulement. Certaines erreurs typiques sont évitées :

  • Toutes les variables globales doivent être déclarées avec var.
  • Un Array ne peut pas avoir d'espaces vides ; écrire au-delà de la longueur lève une TypeError.
  • eval n'est supporté qu'indirectement via la fonction globale eval.
  • Pas de new Number(1) ; le boxing est non supporté.
  • String.prototype.toLowerCase()/toUpperCase() ne gèrent que l'ASCII.
  • RegExp utilise la mise en casse ASCII uniquement.

Ces restrictions rendent le moteur prévisible et économisent de la mémoire.

Performance et Benchmarks

MicroQuickJS passe le micro-benchmark de QuickJS en temps comparable tout en utilisant environ la moitié de la mémoire sur du matériel 32‑bits. Des tests supplémentaires montrent qu’il peut exécuter le benchmark Octane de V8 en mode strict avec un facteur de 1–2× de pénalité de vitesse mais un impact mémoire 4× plus faible.

make test
# Tous les tests unitaires passent.
make microbench
# Utilisation mémoire de 10 kB, exécution du script ~120 ms sur un RasPi‑Zero.

Résumé

MicroQuickJS offre une solution convaincante pour les développeurs qui ont besoin d’un runtime JavaScript sur de petits appareils. Son empreinte de 10 kB en RAM, son sous‑ensemble strict ES5 et son ramasse-miettes de traçage en font une excellente option pour les firmwares, les passerelles IoT ou tout système embarqué qui profite de la flexibilité de JavaScript sans le surcoût d’un moteur volumineux. Que vous construisiez directement avec le REPL, embarquiez l’API C dans votre firmware ou expédiiez du bytecode pré‑compilé, MicroQuickJS propose une voie simplifiée vers JavaScript sur le périmètre.

Original Article: Voir l’original

Partager cet article