Fabrice Bellard dévoile MicroQuickJS
(github.com/bellard)- 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
mqjspermet 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
mqjset 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
- Exemple :
- L’option
-opermet d’enregistrer le bytecode compilé dans un fichier- Le bytecode enregistré peut être exécuté avec
./mqjs mandelbrot.bin
- Le bytecode enregistré peut être exécuté avec
- Le bytecode diffère selon l’endianness du CPU et la taille des mots (32/64 bits), et l’option
-m32permet de générer du bytecode pour 32 bits - L’option
--no-columnpermet 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é
withest impossible et les variables globales doivent impérativement être déclarées avecvar - Les trous (holes) dans les tableaux ne sont pas autorisés
- Exemple :
a[10] = 2provoque une TypeError - Si un tableau avec des trous est nécessaire, il faut utiliser un objet ordinaire
- Exemple :
- 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 inne parcourt que les propriétés propres de l’objet, etfor ofn’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
API C
- Dépendances minimales à la bibliothèque C, sans utilisation de
malloc,freeniprintf - 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)
- Exemple :
- 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
JSValueJS_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 viaJS_LoadBytecode()etJS_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
- 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
- Exécution :
Licence
- Distribué sous licence MIT
- Le copyright du code source appartient à Fabrice Bellard et Charlie Gordon
2 commentaires
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
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
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
On sépare parseur et lexer, mais on voit rarement des tentatives de remplacer des tokens comme
then/endpar{}Discussions associées : fil HN, discussion Reddit
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
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
Avec l’option
emcc -O3ou en ajoutant--closure 1, ça devrait pouvoir être réduit davantageQuickJS 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
Lien JSLinux
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
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
Les énigmes JS de YouTube sont tellement complexes que même un émulateur JS écrit en Python a fini par abandonner
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
MicroQuickJS n’implémente qu’une partie d’ES5 et ne fournit pas de bindings d’environnement
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 timing compte énormément. Quand ça a été posté hier soir, il n’y a eu aucune 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
Son approche consistant à créer des programmes complets avec un minimum de dépendances et d’outils est impressionnante
Parce que si c’est une vraie personne, il faut bien qu’elle dorme
ts_server, TextSynth
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
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
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