7 points par GN⁺ 2025-12-24 | 2 commentaires | Partager sur WhatsApp
  • MicroQuickJS(MQuickJS) est un moteur JavaScript ultra-léger conçu pour les systèmes embarqués, capable de fonctionner avec seulement environ 10 kB de RAM et 100 kB de ROM
  • Il adopte un ramasse-miettes par traçage et un stockage des chaînes en UTF-8 afin de réduire l’usage mémoire tout en conservant des performances proches de QuickJS
  • Le langage pris en charge est un sous-ensemble limité de JavaScript proche d’ES5, et seul le mode strict (strict mode) est autorisé afin d’interdire les syntaxes sujettes aux erreurs
  • L’outil REPL mqjs permet d’exécuter des scripts, d’enregistrer du bytecode et de définir des limites mémoire, et le bytecode généré peut être exécuté directement depuis la ROM
  • L’ensemble du moteur et de la bibliothèque standard réside en ROM, ce qui permet une initialisation rapide et une faible consommation mémoire, et améliore l’efficacité d’exécution de JavaScript en environnement embarqué

Introduction

  • MicroQuickJS(MQuickJS) est un moteur JavaScript destiné aux systèmes embarqués, fonctionnant avec 10 kB de RAM et 100 kB de ROM (code ARM Thumb-2 inclus)
    • Les performances sont proches de celles de QuickJS
  • Il ne prend en charge qu’un sous-ensemble proche d’ES5 et fonctionne uniquement en mode strict (strict mode), qui interdit les syntaxes inefficaces ou sujettes aux erreurs
  • Il partage une partie du code avec QuickJS, mais sa structure interne a été entièrement repensée pour économiser la mémoire
    • Utilisation d’un ramasse-miettes par traçage, de l’absence d’usage de la pile CPU et du stockage des chaînes en UTF-8

REPL

  • La commande REPL est mqjs et prend en charge l’exécution de scripts, l’évaluation, le mode interactif, la définition de limites mémoire et l’enregistrement du bytecode
    • Exemple : ./mqjs --memory-limit 10k tests/mandelbrot.js
  • L’option -o permet d’enregistrer le bytecode compilé dans un fichier
    • Le bytecode enregistré peut être exécuté avec ./mqjs mandelbrot.bin
    Publicité
  • Le bytecode diffère selon l’endianness du CPU et la taille des mots (32/64 bits), et l’option -m32 permet de générer du bytecode pour 32 bits
  • L’option --no-column permet de supprimer les numéros de colonne des informations de débogage

Mode strict

  • Seul le strict mode est autorisé ; l’usage du mot-clé with est impossible et les variables globales doivent impérativement être déclarées avec var
  • Les trous (holes) dans les tableaux ne sont pas autorisés
    • Exemple : a[10] = 2 provoque une TypeError
    • Si un tableau avec des trous est nécessaire, il faut utiliser un objet ordinaire
  • Seul l’eval global est pris en charge, sans accès aux variables locales
  • Le value boxing n’est pas pris en charge (new Number(1), etc.)

Sous-ensemble JavaScript

  • Basé sur le strict mode, avec une compatibilité principalement orientée ES5
  • L’objet Array ne contient pas de trous, et l’accès à un index hors limites provoque une erreur
  • for in ne parcourt que les propriétés propres de l’objet, et for of n’est pris en charge que pour les tableaux
  • L’objet global existe, mais sans getter/setter, et les propriétés créées directement n’apparaissent pas comme variables globales
  • Les expressions régulières (Regexp) ne gèrent la casse qu’en ASCII, et /./ correspond par point de code Unicode plutôt qu’en UTF-16
  • Les fonctions sur les chaînes ne traitent que l’ASCII (toLowerCase, toUpperCase)
  • Date ne prend en charge que Date.now()
  • Fonctions supplémentaires prises en charge :
    • for of, Typed arrays, littéraux de chaîne \u{hex}
    • Fonctions Math : imul, clz32, fround, trunc, log2, log10
    • Opérateur d’exponentiation, flags d’expressions régulières (s, y, u), fonctions sur les chaînes (replaceAll, trimStart, trimEnd), globalThis
Publicité

API C

  • Dépendances minimales à la bibliothèque C, sans utilisation de malloc, free ni printf
  • Il faut fournir directement un tampon mémoire, et le moteur n’alloue de mémoire qu’à l’intérieur de ce tampon
    • Exemple : ctx = JS_NewContext(mem_buf, sizeof(mem_buf), &js_stdlib)
  • En raison du mode de fonctionnement du ramasse-miettes, il n’est pas nécessaire d’appeler JS_FreeValue()
  • L’adresse des objets pouvant changer à chaque allocation, il est recommandé d’utiliser des pointeurs JSValue
    • JS_PushGCRef() / JS_PopGCRef() permettent une gestion sûre des références
  • La bibliothèque standard est compilée en structures C pouvant être stockées en ROM, ce qui permet une initialisation rapide et une faible utilisation de la RAM
  • L’exécution du bytecode est possible depuis la ROM, après relocalisation avec JS_RelocateBytecode(), puis exécution via JS_LoadBytecode() et JS_Run()
  • Une bibliothèque mathématique (libm.c) et un émulateur de virgule flottante sont intégrés

Structure interne et comparaison avec QuickJS

  • Ramasse-miettes : utilisation d’un GC par traçage et compactage au lieu du comptage de références
    • Cela évite la fragmentation mémoire et réduit la taille des objets
  • Représentation des valeurs : conception adaptée à la taille des mots du CPU (32/64 bits)
    • Peut stocker des entiers sur 31 bits, des points de code Unicode, des flottants et des pointeurs vers des blocs mémoire
  • Les chaînes sont stockées en UTF-8, ce qui est plus efficace que l’approche en tableaux 8/16 bits de QuickJS
  • Les fonctions C peuvent être stockées comme une valeur unique, sans possibilité d’ajouter des propriétés
  • La bibliothèque standard réside en ROM, avec un minimum d’objets en RAM, ce qui permet une initialisation rapide du moteur
  • Le bytecode est basé sur une pile et traité en lecture seule via une table de références indirectes
    • Les numéros de ligne et de colonne sont compressés avec un code de Golomb
    Publicité
  • Le compilateur est similaire à celui de QuickJS, mais utilise un parseur non récursif pour limiter l’utilisation de la pile C
    • Génération du bytecode en un seul passage, sans arbre syntaxique

Tests et benchmarks

  • Test de base : make test
  • Micro-benchmarks QuickJS : make microbench
  • Le benchmark Octane (version modifiée pour le mode strict) peut être téléchargé séparément
    • Exécution : make octane

Licence

  • Distribué sous licence MIT
  • Le copyright du code source appartient à Fabrice Bellard et Charlie Gordon

2 commentaires

 
GN⁺ 2025-12-24
Commentaires sur Hacker News
  • Si ça avait existé en 2010, le langage de script de Redis aurait sans doute été JavaScript plutôt que Lua
    Lua a été choisi non pas pour des raisons liées au langage, mais à cause de contraintes d’implémentation (petit, rapide, basé sur ANSI C)
    Certaines idées de Lua sont bonnes, mais personnellement, le fait de s’éloigner d’une syntaxe de la famille Algol m’a semblé inutile
    La confusion que créent de nouveaux concepts d’abstraction, comme en Smalltalk ou en FORTH, vaut la peine, mais je ne pense pas que les changements de Lua soient justifiés au même niveau

    • Je n’aime pas particulièrement la syntaxe de Lua, mais je pense que les raisons qui ont motivé son choix par les développeurs sont tout à fait compréhensibles
      Lua est le seul langage léger à prendre en charge la tail call optimization (TCO), ce qui permet d’écrire des programmes uniquement avec de la récursion, sans boucles
      JavaScript n’a pas cette optimisation, donc on ne peut pas faire la même chose
      Lua est aussi particulièrement adapté à l’écriture de compilateurs, à cause de ses nombreuses structures récursives
      JS est peut-être mieux adapté au scripting dans Redis, mais c’est dommage de rabaisser Lua
    • Étant donné que Lua est apparu pour la première fois en 1993, sa syntaxe était assez traditionnelle pour l’époque
      Au Brésil, Pascal et Ada étaient plus utilisés que C, d’où cette influence
      Ruby et Perl sont sortis à peu près à la même période, mais avec des changements de syntaxe bien plus radicaux
    • J’allais d’abord dire que j’avais appris Lua facilement à 13 ans, puis je me suis arrêté en réalisant que l’auteur du commentaire était antirez lui-même
    • Ça ne résoudra peut-être pas les problèmes de syntaxe, mais le concept de « language skins » est intéressant
      On sépare parseur et lexer, mais on voit rarement des tentatives de remplacer des tokens comme then/end par {}
      Discussions associées : fil HN, discussion Reddit
    • Je me demande si Tcl a déjà été envisagé comme langage de script pour Redis, puisque c’est le langage embarqué d’origine
  • Ce moteur restreint JS exactement comme je l’aurais voulu à l’époque où je travaillais sur JSC
    Sur le Web, ce genre de contrainte est impossible pour des raisons de compatibilité, mais dans un environnement embarqué, cela peut au contraire devenir un choix de conception réjouissant

    • On a déjà des moteurs JS sans ce genre de restrictions
    • Je me demande ce qu’est devenu le travail sur le multithreading dans JSC. Est-ce que ça s’est arrêté après son départ d’Apple, ou est-ce que le code existe encore ?
  • J’ai créé un playground pour exécuter MicroQuickJS directement dans le navigateur
    Version WebAssembly de MicroQuickJS
    À noter qu’il existe aussi la version QuickJS d’origine
    QuickJS fait 2,28 Mo, MicroQuickJS seulement 303 Ko, donc c’est beaucoup plus léger

    • Le binaire semble plus gros parce que des informations comme les noms sont incluses dans le build
      Avec l’option emcc -O3 ou en ajoutant --closure 1, ça devrait pouvoir être réduit davantage
      QuickJS est déjà optimisé, alors que MicroQuickJS a encore une marge d’amélioration
  • Comme le disait Jeff Atwood dans sa célèbre formule, « toute application qui peut être écrite en JavaScript finira par l’être »
    On dirait que cela s’applique maintenant aussi aux systèmes embarqués
    Wiki Jeff Atwood

    • D’accord. Conférence liée : The Birth and Death of JavaScript
    • Fabrice Bellard a aussi créé une VM en JavaScript capable de faire tourner Linux dans le navigateur
      Lien JSLinux
    • Ça ressemble presque à une Rule 35 d’Internet
  • C’est dommage que cela ait été publié sans historique de commits
    J’aurais aimé voir à quelle vitesse quelqu’un de ce niveau termine un projet
    De toute façon, comme c’est basé sur QuickJS, la comparaison n’aurait sans doute pas beaucoup de sens

    • L’expression « public repository of… » laisse penser que l’historique réel du travail est peut-être dans un dépôt privé
    • Il l’a peut-être tout simplement achevé en un seul jet
  • Je me demande si ce pourrait être la manière la plus légère de résoudre le challenge JS de YouTube dans yt-dlp
    Voir la documentation EJS de yt-dlp
    QuickJS est déjà pris en charge

    • Peu probable. Il ne prend en charge qu’une implémentation partielle du niveau ES5
      Les énigmes JS de YouTube sont tellement complexes que même un émulateur JS écrit en Python a fini par abandonner
    • Avec seulement une implémentation d’ES5, ça semble difficilement réaliste
  • Je ne connais pas bien les systèmes embarqués, mais je me demande si ce moteur pourrait permettre de programmer un ESP32 ou un Arduino en JavaScript
    Un peu comme MicroPython

    • Il existe déjà des projets similaires
    • Le moteur XS de Moddable/Kinoma prend en charge ES6 et au-delà
      MicroQuickJS n’implémente qu’une partie d’ES5 et ne fournit pas de bindings d’environnement
    • Il y avait autrefois une carte de programmation JS appelée Tessel
      Elle convertissait le code JS en bytecode de VM Lua pour l’exécuter, ce qui était une approche assez astucieuse
      Récemment, j’ai même réécrit cet ancien CLI Node 0.8 en Rust, mais le matériel a fini par retourner dans un tiroir
    • Le point essentiel, c’est une architecture sans malloc(). C’est ce qui ouvre des possibilités
  • Le timing compte énormément. Quand ça a été posté hier soir, il n’y a eu aucune réaction

    • C’est sans doute juste une question de chance
    • D’autres ont essayé aussi sans obtenir de réaction
      Il existe une stratégie qui consiste à republier le matin aux États-Unis, ou à reposter périodiquement
  • Fabrice Bellard est l’un des programmeurs les plus productifs et polyvalents encore en activité
    Œuvres majeures : FFmpeg, QEMU, JSLinux, TCC, QuickJS
    Une véritable légende

    • Vu le respect qu’il inspire, peu de gens s’intéressent vraiment à sa manière de développer
      Son approche consistant à créer des programmes complets avec un minimum de dépendances et d’outils est impressionnante
    • Je finis par me demander s’il n’est pas, au fond, non pas une seule personne mais le nom de code d’un collectif de hackers chevronnés
      Parce que si c’est une vraie personne, il faut bien qu’elle dorme
    • Il a aussi développé lui-même un moteur d’inférence LLM, qu’il maintient depuis l’époque de GPT-2
      ts_server, TextSynth
    • Fait intéressant, la plupart des programmes qu’il crée ne traitent pas d’interfaces utilisateur centrées sur la GUI
      Il semble préférer une structure où l’utilisateur règle les paramètres, puis où le programme s’exécute de façon autonome jusqu’au bout
    • Il a aussi remporté à trois reprises le Concours international de code C obscurci (IOCCC)
      Liste des lauréats de l’IOCCC
  • Le fait qu’on puisse « compiler et exécuter du JS avec seulement 10 Ko de RAM » est impressionnant
    Le moment est bien choisi, à une époque où la RAM redevient chère
    Je me demande si cela pourrait être intégré à Chromium ou Electron

    • Ce serait difficile à cause de la compatibilité Web, mais de toute façon, l’impact sur la mémoire de Chromium ne serait probablement pas énorme
 
xguru 2025-12-24

Pour une présentation de Fabrice Bellard, vous pouvez vous référer à ce que j’avais écrit en commentaire auparavant. Une personne d’une constance impressionnante, un monstre de talent…
https://fr.news.hada.io/topic?id=59#cid51