Motivation : le document QuickJs Javascript Engine est un peu spartiate et j'ai dû faire des efforts pour découvrir les fonctionnalités de QuickJs. Cette application permet d'utiliser le langage javascript en dehors de tout context web.
Note : Cette traduction a été faite à l'aide de l'excellent deepl.
QuickJS est un petit moteur Javascript intégrable. Il supporte la spécification ES2020 1, y compris les modules, les générateurs asynchrones, les proxies et BigInt.
Il prend en charge les extensions mathématiques telles que les grands nombres décimaux flottants (BigDecimal), les grands nombres binaires à virgule flottante (BigFloat) et la surcharge des opérateurs.
Principales caractéristiques
Petit et facilement intégrable : juste quelques fichiers C, aucune dépendance externe, 210 KiB de code x86 pour un simple programme "hello world".
Interprète rapide avec un temps de démarrage très faible : il exécute les 69 000 tests de la suite de tests ECMAScript2 en 95 secondes environ sur un seul cœur d'un PC de bureau. Le cycle de vie complet d'une instance d'exécution s'achève en moins de 300 microsecondes.
Prise en charge presque complète de la norme ES2020, y compris les modules, les générateurs asynchrones et la prise en charge complète de l'annexe B (compatibilité avec les sites web hérités). De nombreuses fonctionnalités de la future spécification ES2021 3 sont également prises en charge.
Réussit près de 100 % des tests de la suite de tests ECMAScript lors de la sélection des fonctionnalités ES2020.
Compile les sources Javascript en exécutables sans dépendance externe.
Ramasse miettes utilisant le comptage de références (pour réduire l'utilisation de la mémoire et avoir un comportement déterministe) avec élimination des cycles.
Interprète de ligne de commande avec colorisation contextuelle et complétion implémentée en Javascript.
Petite bibliothèque standard intégrée avec des wrappers de bibliothèque C.
Utilisation
Installation
Un fichier Makefile est fourni pour compiler le moteur sous Linux ou MacOS/X. Un support Windows préliminaire est disponible par compilation croisée sur un hôte Linux avec les outils MingGW.
Modifiez le début du Makefile si vous souhaitez sélectionner des options spécifiques, puis lancez make.
Vous pouvez taper make install en tant que root si vous souhaitez installer les binaires et les fichiers de support dans /usr/local (ce n'est pas nécessaire pour utiliser QuickJS).
Démarrage rapide
qjs est l'interpréteur en ligne de commande (REPL : boucle lecture-évaluation-impression). Vous pouvez passer des fichiers Javascript et/ou des expressions comme arguments pour les exécuter : ./qjs examples/hello.js
qjsc est le compilateur en ligne de commande :
./qjsc -o hello examples/hello.js./hello
génère un exécutable hello sans dépendances extérieures
Options de la ligne de commande
qjs interpréteur
Utilisation : qjs [options] [fichier] [arguments]
Les options sont :
-h --help
liste les options
-e EXPR --eval EXPR
évalue EXPR
-i --interactive
passe en mode interactif (ce n'est pas le mode par défaut lorsque des fichiers sont fournis)
-m --module
Charger en tant que module ES6 (défaut=autodétection). Un module est auto-détecté si l'extension du nom de fichier est .mjs ou si le premier mot-clé de la source est import.
--script
charge un ES6 script (défaut=autodetecte)
--bignum
Activer les extensions bignum : objet BigDecimal, objet BigFloat et directive "use math".
-I fichier --include fichier
inclure un fichier supplémentaire
--std
Rend les modules std et os disponibles pour le script chargé, même s'il ne s'agit pas d'un module.
-d --dump
Vider les statistiques d'utilisation de la mémoire.
-q --quitter
instancie l'interpréteur et quitte.
Compilateur qjsc
Utilisation : qjsc [options] [fichiers]
Les options sont les suivantes :
-c
Ne sort que le bytecode dans un fichier C. La valeur par défaut est un fichier exécutable.
-e
Produit le main() et le bytecode dans un fichier C. La valeur par défaut est la sortie d'un fichier exécutable.
-o sortie
Définit le nom du fichier de sortie (par défaut = out.c ou a.out).
-N nomc
Définit le nom C des données générées.
-m
Compile en tant que module Javascript (par défaut=autodétection).
-D nom_du_module
Compile un module chargé dynamiquement et ses dépendances. Cette option est nécessaire lorsque votre code utilise le mot-clé import ou le constructeur os.Worker car le compilateur ne peut pas trouver statiquement le nom des modules chargés dynamiquement.
-M nom_du_module[,nom_du_c]
Ajoute du code d'initialisation pour un module C externe. Voir l'exemple c_module.
-x
Sortie avec échange d'octets (utilisée uniquement pour la compilation croisée).
-flto
Utiliser l'optimisation du temps de liaison. La compilation est plus lente mais l'exécutable est plus petit et plus rapide. Cette option est automatiquement activée lorsque les options -fno-x sont utilisées.
Désactive certaines fonctionnalités du langage pour produire un fichier exécutable plus petit.
-fbignum
Active les extensions bignum : objet BigDecimal, objet BigFloat et directive "use math".
Application qjscalc
L'application qjscalc est un surensemble de l'interpréteur de ligne de commande qjs implémentant une calculatrice Javascript avec des nombres entiers et flottants arbitrairement grands, des fractions, des nombres complexes, des polynômes et des matrices. Le code source se trouve dans qjscalc.js. Une documentation plus complète et une version web sont disponibles à l'adresse http://numcalc.com.
Tests intégrés
Lancez make test pour exécuter les quelques tests intégrés inclus dans l'archive QuickJS.
Test262 (Suite de tests ECMAScript)
Un exécuteur test262 est inclus dans l'archive QuickJS. Les tests test262 peuvent être installés dans le répertoire source de QuickJS avec :
Le patch ajoute les harness fonctions spécifiques à l'implémentation et optimise les tests inefficaces des classes de caractères RegExp et des échappements de propriétés Unicode (les tests eux-mêmes ne sont pas modifiés, seule une fonction lente d'initialisation des chaînes de caractères est optimisée).
Les tests peuvent être exécutés avec : make test2.
Les fichiers de configuration test262.conf (resp. test262o.conf pour les anciens tests ES5.14)) contiennent les options pour exécuter les différents tests. Les tests peuvent être exclus en fonction des caractéristiques ou du nom de fichier.
Le fichier test262_errors.txt contient la liste courante des erreurs. Le programme d'exécution affiche un message lorsqu'une nouvelle erreur apparaît ou lorsqu'une erreur existante est corrigée ou modifiée. Utilisez l'option -u pour mettre à jour la liste actuelle des erreurs (ou faites test2-update).
Le fichier test262_report.txt contient les logs de tous les tests. Il est utile pour avoir une analyse plus claire d'une erreur particulière. En cas de crash, la dernière ligne correspond au test défaillant.
Utilisez la syntaxe ./run-test262 -c test262.conf -f filename.js pour lancer un seul test. Utilisez la syntaxe ./run-test262 -c test262.conf N pour démarrer le test au numéro N.
Pour plus d'informations, exécutez ./run-test262 pour voir les options de ligne de commande du programme d'exécution test262.
run-test262 accepte l'option -N pour être invoqué à partir de test262-harness5 via eshost. A moins que vous ne souhaitiez comparer QuickJS avec d'autres moteurs dans les mêmes conditions, nous ne recommandons pas d'exécuter les tests de cette manière car c'est beaucoup plus lent (typiquement une demi-heure au lieu d'environ 100 secondes).
NDLR : Je n'ai pas contrôlé la traduction produite par deepl, parce que n'y comprends que couic.
Spécifications techniques
Prise en charge des langues
Prise en charge de l'ES2020
La spécification ES2020 est presque entièrement prise en charge, y compris l'annexe B (compatibilité web héritée) et les fonctionnalités liées à Unicode.
Les fonctionnalités suivantes ne sont pas encore prises en charge : Tail calls
ECMA402
L'ECMA402 (API d'internationalisation) n'est pas pris en charge.
Extensions
La directive "use strip" indique que les informations de débogage (y compris le code source des fonctions) ne doivent pas être conservées pour économiser de la mémoire. Comme "use strict", la directive peut être globale à un script ou locale à une fonction.
La première ligne d'un script commençant par #! est ignorée.
Les extensions mathématiques sont entièrement rétrocompatibles avec le Javascript standard. Voir jsbignum.pdf pour plus d'informations.
Prise en charge de BigDecimal : grands nombres arbitraires à virgule flottante en base 10.
Support des BigFloat : grands nombres arbitraires à virgule flottante en base 2.
Surcharge des opérateurs.
La directive "use bigint" active le mode bigint où les entiers sont BigInt par défaut.
La directive "use math" active le mode math où les opérateurs de division et de puissance sur les entiers produisent des fractions. Les littéraux en virgule flottante sont BigFloat par défaut et les entiers sont BigInt par défaut.
Modules
Les modules ES6 sont entièrement pris en charge. La résolution des noms par défaut est la suivante :
Les noms de modules précédés d'un . ou d'un .. sont relatifs au chemin du module actuel.
Les noms de modules sans . ou .. sont des modules système, tels que std ou os.
Les noms de modules se terminant par .so sont des modules natifs utilisant l'API C de QuickJS.
Bibliothèque standard
La bibliothèque standard est incluse par défaut dans l'interpréteur de ligne de commande. Elle contient les deux modules std et os ainsi que quelques objets globaux.
Objets globaux
scriptArgs
Fournit les arguments de la ligne de commande. Le premier argument est le nom du script.
print(...args)
Affiche les arguments séparés par des espaces et une nouvelle ligne à la fin.
console.log(...args)
Identique à print().
module std
Le module std fournit des enveloppes aux libc stdlib.h et stdio.h ainsi qu'à quelques autres utilitaires.
NDLT : Préfixer ces fonctions avec std, exemple std.open( ...
Sont disponibles :
exit(n)
Quitte le processus.
evalScript(str, options = undefined)
Évalue la chaîne str comme un script (global eval). options est un objet optionnel contenant les propriétés optionnelles suivantes :
backtrace_barrier : Booléen (par défaut = false). Si true, les backtraces d'erreur ne listent pas les images de la pile en dessous de l'evalScript.
loadScript(fichier)
Évalue le nom du fichier en tant que script (eval global).
loadFile(nomdefichier)
Charge le nom du fichier et le renvoie sous forme de chaîne de caractères en supposant un encodage UTF-8. Retourne null en cas d'erreur d'entrée/sortie.
Ouvre un fichier (enveloppe de la libc fopen()). Retourne l'objet FILE ou null en cas d'erreur d'E/S. Si errorObj n'est pas undefined, sa propriété errno reçoit le code d'erreur ou 0 si aucune erreur ne s'est produite.
popen(command, flags, errorObj = undefined)
Ouvre un processus en créant un pipe (wrapper de la libc popen()). Retourne l'objet FILE ou null en cas d'erreur d'entrée/sortie. Si errorObj n'est pas undefined, sa propriété errno reçoit le code d'erreur ou 0 si aucune erreur ne s'est produite.
fdopen(fd, flags, errorObj = undefined)
Ouvre un fichier à partir d'un handle de fichier (wrapper de la libc fdopen()). Retourne l'objet FILE ou null en cas d'erreur d'E/S. Si errorObj n'est pas undefined, sa propriété errno est fixée au code d'erreur ou à 0 si aucune erreur ne s'est produite.
tmpfile(errorObj = undefined)
Ouvre un fichier temporaire. Retourne l'objet FILE ou null en cas d'erreur d'entrée/sortie. Si errorObj n'est pas undefined, sa propriété errno prend la valeur du code d'erreur ou 0 si aucune erreur ne s'est produite.
puts(str)
Équivalent à std.out.puts(str).
printf(fmt, ...args)
Equivalent à std.out.printf(fmt, ...args).
sprintf(fmt, ...args)
Equivalent à la fonction sprintf() de la librairie.
in, out, err
Enveloppe des fichiers libc stdin, stdout, stderr.
SEEK_SET, SEEK_CUR, SEEK_END
Constantes pour seek().
Error
Objet d'énumération contenant la valeur entière des erreurs courantes (des codes d'erreur supplémentaires peuvent être définis) :
EINVAL, EIO, EACCES, EEXIST, ENOSPC, ENOSYS, EBUSY,, ENOENT, EPERM, EPIPE
strerror(errno)
Renvoie une chaîne de caractères décrivant l'erreur errno.
gc()
Invoque manuellement l'algorithme d'élimination des cycles. L'algorithme d'élimination des cycles est automatiquement lancé lorsque cela est nécessaire, cette fonction est donc utile en cas de contraintes de mémoire spécifiques ou pour des tests.
getenv(nom)
Renvoie la valeur de la variable d'environnement nom ou undefined si elle n'est pas définie.
setenv(nom, valeur)
Fixe la valeur de la variable d'environnement nom à la valeur de la chaîne de caractères.
unsetenv(nom)
Supprime la variable d'environnement nom.
getenviron()
Renvoie un objet contenant les variables d'environnement sous forme de paires clé-valeur.
urlGet(url, options = undefined)
Télécharge l'url en utilisant l'utilitaire de ligne de commande curl. options est un objet optionnel contenant les propriétés optionnelles suivantes : binary
Booléen (par défaut = false). Si true, la réponse est un ArrayBuffer au lieu d'une chaîne. Lorsqu'une chaîne est renvoyée, les données sont supposées être encodées en UTF-8. full
Booléen (par défaut = false). Si true, un objet contenant les propriétés response (contenu de la réponse), responseHeaders (en-têtes séparés par CRLF), status (code d'état) est retourné. response est null en cas d'erreur de protocole ou de réseau. Si full est false, seule la réponse est renvoyée si le statut est compris entre 200 et 299. Sinon, null est renvoyé.
parseExtJSON(str)
Analyse str en utilisant un super-ensemble de JSON.parse. Les extensions suivantes sont acceptées :
- Commentaires sur une ou plusieurs lignes
- propriétés non citées (identifiants Javascript en ASCII uniquement)
- virgule de fin dans les définitions de tableaux et d'objets
- chaînes de caractères entre guillemets simples
- \f et \v sont acceptés comme caractères d'espacement
- Plus de tête dans les nombres
- les nombres octaux (préfixe 0o) et hexadécimaux (préfixe 0x)
Prototype de FILE :
close()
Ferme le fichier. Retourne 0 si OK ou -errno en cas d'erreur d'entrée/sortie.
puts(str)
Produit la chaîne de caractères avec l'encodage UTF-8.
printf(fmt, ...args)
Impression formatée.
Les mêmes formats que printf de la bibliothèque C standard sont supportés. Les types de format d'entier (par exemple %d) tronquent les nombres ou les BigInts à 32 bits. Utilisez le modificateur l (par exemple %ld) pour tronquer à 64 bits.
flush()
Vide le fichier mis en mémoire tampon.
seek(offset, whence)
Recherche une position de fichier donnée (whence est std.SEEK_*). offset peut être un nombre ou un bigint. Retourne 0 si OK ou -errno en cas d'erreur d'E/S.
tell()
Retourne la position actuelle du fichier.
tello()
Retourne la position actuelle du fichier sous la forme d'un bigint.
eof()
Retourne vrai si fin de fichier.
fileno()
Retourne le handle OS associé.
error()
Retourne true s'il y a eu une erreur.
clearerr()
Efface l'indication d'erreur.
read(buffer, pos, len)
Lit len octets du fichier dans le tampon ArrayBuffer buffer à la position de l'octet pos (wrapper de libc fread).
write(fd, buffer, offset, length)
Écrit la longueur des octets dans le fichier fd à partir du tampon ArrayBuffer à la position d'octet offset. Retourne le nombre d'octets écrits ou < 0 en cas d'erreur.
isatty(fd)
Retourne true si fd est un handle TTY (terminal).
ttyGetWinSize(fd)
Retourne la taille du TTY sous la forme [width, height] ou null si elle n'est pas disponible.
ttySetRaw(fd)
Mettre le TTY en mode brut.
remove(nom de fichier)
Supprime un fichier. Retourne 0 si OK ou -errno.
rename(ancien nom, nouveau nom)
Renomme un fichier. Retourne 0 si OK ou -errno.
realpath(chemin)
Retourne [str, err] où str est le nom de chemin absolu canonisé de path et err le code d'erreur.
getcwd()
Retourne [str, err] où str est le répertoire de travail courant et err le code d'erreur.
chdir(chemin)
Change le répertoire courant. Retourne 0 si OK ou -errno.
mkdir(chemin, mode = 0o777)
Crée un répertoire à l'emplacement path. Retourne 0 si OK ou -errno.
stat(chemin)
lstat(chemin)
Retourne [obj, err] où obj est un objet contenant l'état du fichier du chemin. err est le code d'erreur. Les champs suivants sont définis dans obj : dev, ino, mode, nlink, uid, gid, rdev, size, blocks, atime, mtime, ctime. Les temps sont spécifiés en millisecondes depuis 1970. lstat() est identique à stat() à l'exception du fait qu'il renvoie des informations sur le lien lui-même.
Constantes permettant d'interpréter la propriété mode renvoyée par stat(). Elles ont la même valeur que dans l'en-tête du système C sys/stat.h.
utimes(path, atime, mtime)
Modifie les temps d'accès et de modification du chemin d'accès au fichier. Les temps sont spécifiés en millisecondes depuis 1970. Retourne 0 si OK ou -errno.
symlink(target, linkpath)
Crée un lien au chemin d'accès contenant la chaîne de caractères cible. Retourne 0 si OK ou -errno.
readlink(chemin)
Retourne [str, err] où str est la cible du lien et err le code d'erreur.
readdir(chemin)
Retourne [array, err] où array est un tableau de chaînes contenant les noms de fichiers du chemin d'accès au répertoire. err est le code d'erreur.
setReadHandler(fd, func)
Ajoute un gestionnaire de lecture au gestionnaire de fichier fd. func est appelé chaque fois que des données sont en attente pour fd. Un seul gestionnaire de lecture par gestionnaire de fichier est supporté. Utilisez func = null pour supprimer le gestionnaire.
setWriteHandler(fd, func)
Ajoute un gestionnaire d'écriture à la poignée de fichier fd. func est appelé chaque fois que des données peuvent être écrites sur fd. Un seul gestionnaire d'écriture par poignée de fichier est pris en charge. Utilisez func = null pour supprimer le gestionnaire.
signal(signal, func)
Appelle la fonction func lorsque le signal se produit. Un seul gestionnaire par numéro de signal est supporté. Utilisez null pour définir le gestionnaire par défaut ou undefined pour ignorer le signal. Les gestionnaires de signaux ne peuvent être définis que dans le thread principal.
SIGINT, SIGABRT, SIGFPE, SIGILL, SIGSEGV, SIGTERM
Numéros de signaux POSIX.
kill(pid, sig)
Envoie le signal sig au processus pid.
exec(args[, options])
Exécute un processus avec les arguments args. options est un objet contenant des paramètres optionnels : block
Booléen (par défaut = true). Si true, attendre que le processus se termine. Dans ce cas, exec renvoie le code de sortie s'il est positif ou le numéro du signal négatif si le processus a été interrompu par un signal. Si false, ne pas bloquer et retourner l'identifiant du processus enfant. usePath
Booléen (par défaut = true). Si true, le fichier est recherché dans la variable d'environnement PATH. file
Chaîne (par défaut = args[0]). Définit le fichier à exécuter. cwd
Chaîne. S'il est présent, définit le répertoire de travail du nouveau processus. stdin stdout stderr
S'il est présent, définit l'identifiant de stdin, stdout ou stderr dans le processus enfant. env
Objet. S'il est présent, définit l'environnement du processus à partir des paires clé-valeur de l'objet. Sinon, utiliser le même environnement que le processus en cours. uid
Entier. S'il est présent, l'uid du processus avec setuid. gid
Entier. S'il est présent, le gid du processus avec setgid.
waitpid(pid, options)
waitpid Appel système Unix. Retourne le tableau [ret, status]. ret contient -errno en cas d'erreur.
WNOHANG
Constante pour l'argument options de waitpid.
dup(fd)
Appel système Unix dupliqué.
dup2(oldfd, newfd)
Appel système Unix dup2.
pipe()
Appel système Unix pipe. Retourne deux handles [read_fd, write_fd] ou null en cas d'erreur.
sleep(delay_ms)
Dort pendant delay_ms millisecondes.
setTimeout(func, delay)
Appelle la fonction func après delay ms. Retourne un handle au timer.
clearTimeout(handle)
Annule un timer.
plateform
Renvoie une chaîne de caractères représentant la plate-forme : "linux", "darwin", "win32" ou "js".
Worker(nom_du_module)
Constructeur permettant de créer un nouveau thread (worker) avec une API proche des WebWorkers. module_filename est une chaîne de caractères spécifiant le nom du module qui sera exécuté dans le thread nouvellement créé. Pour les modules importés dynamiquement, il est relatif au script actuel ou au chemin du module. Les threads ne partagent normalement aucune donnée et communiquent entre eux par le biais de messages. Les workers imbriqués ne sont pas pris en charge. Un exemple est disponible dans tests/test_worker.js.
La classe worker possède les propriétés statiques suivantes : parent
Dans le worker créé, Worker.parent représente le worker parent et est utilisé pour envoyer ou recevoir des messages.
Les instances de workers ont les propriétés suivantes : postMessage(msg)
Envoie un mesage au worker correspondant. msg est cloné dans la destination à l'aide d'un algorithme similaire à l'algorithme de clonage structuré HTML. Les SharedArrayBuffer sont partagés entre les workers.
Limitations actuelles : Map et Set ne sont pas encore supportés. onmessage
Getter et setter. Définit une fonction qui est appelée à chaque fois qu'un message est reçu. La fonction est appelée avec un seul argument. C'est un objet avec une data property contenant le message reçu. Le thread n'est pas terminé s'il existe au moins un gestionnaire onmessage non nul.
API C de QuickJS
L'API C a été conçue pour être simple et efficace. L'API C est définie dans l'en-tête quickjs.h.
Exécution et contextes
JSRuntime représente un runtime Javascript correspondant à un objet heap. Plusieurs runtimes peuvent exister en même temps mais ils ne peuvent pas échanger d'objets. A l'intérieur d'un runtime donné, le multithreading n'est pas supporté.
JSContext représente un contexte Javascript (ou Realm). Chaque JSContext possède ses propres objets globaux et objets système. Il peut y avoir plusieurs JSContexts par JSRuntime et ils peuvent partager des objets, de la même manière que des frames de même origine partagent des objets Javascript dans un navigateur web.
JSValue
JSValue représente une valeur Javascript qui peut être un type primitif ou un objet. Le comptage de références est utilisé, il est donc important de dupliquer (JS_DupValue(), incrémente le comptage de références) ou de libérer (JS_FreeValue(), décrémente le comptage de références) les JSValues de manière explicite.
Fonctions C
Les fonctions C peuvent être créées avec JS_NewCFunction(). JS_SetPropertyFunctionList() est un raccourci pour ajouter facilement des fonctions, des setters et des getters à un objet donné.
Contrairement à d'autres moteurs Javascript intégrés, il n'y a pas de pile implicite, de sorte que les fonctions C reçoivent leurs paramètres comme des paramètres C normaux. En règle générale, les fonctions C prennent des JSValues constantes comme paramètres (elles n'ont donc pas besoin de les libérer) et renvoient une JSValue nouvellement allouée (= live).
Exceptions
Exceptions : la plupart des fonctions C peuvent retourner une exception Javascript. Celle-ci doit être explicitement testée et gérée par le code C. La valeur JS spécifique JS_EXCEPTION indique qu'une exception s'est produite. L'objet d'exception réel est stocké dans le JSContext et peut être récupéré avec JS_GetException().
Évaluation des scripts
Utiliser JS_Eval() pour évaluer un script ou un module source.
Si le script ou le module a été compilé en bytecode avec qjsc, il peut être évalué en appelant js_std_eval_binary(). L'avantage est qu'aucune compilation n'est nécessaire, ce qui est plus rapide et plus petit car le compilateur peut être supprimé de l'exécutable si aucune évaluation n'est nécessaire.
Note : le format du bytecode est lié à une version donnée de QuickJS. De plus, aucun contrôle de sécurité n'est effectué avant son exécution. Le bytecode ne doit donc pas être chargé à partir de sources non fiables. C'est pourquoi il n'y a pas d'option pour sortir le bytecode vers un fichier binaire dans qjsc.
Classes JS
Les C opaque data peuvent être attachées à un objet Javascript. Le type C opaque est déterminé par l'identifiant de classe (JSClassID) de l'objet. La première étape consiste donc à enregistrer un nouvel ID de classe et une nouvelle classe JS (JS_NewClassID(), JS_NewClass()). Ensuite, vous pouvez créer des objets de cette classe avec JS_NewObjectClass() et obtenir ou définir C opaqu avec JS_GetOpaque()/JS_SetOpaque().
Lors de la définition d'une nouvelle classe JS, il est possible de déclarer un finalize qui est appelé lorsque l'objet est détruit. Le finalizer doit être utilisé pour libérer les ressources C. Il n'est pas possible d'exécuter du code JS à partir de celui-ci. Une méthode gc_mark peut être fournie afin que l'algorithme de suppression des cycles puisse trouver les autres objets référencés par cet objet. D'autres méthodes sont disponibles pour définir des comportements exotiques de l'objet.
Les ID de classe sont alloués globalement (c'est-à-dire pour tous les runtimes). Les JSClass sont allouées par JSRuntime. JS_SetClassProto() est utilisé pour définir un prototype pour une classe donnée dans un JSContext donné. JS_NewObjectClass() définit ce prototype dans l'objet créé.
Des exemples sont disponibles dans quickjs-libc.c.
Modules C
Les modules ES6 natifs sont pris en charge et peuvent être liés dynamiquement ou statiquement. Regardez les exemples test_bjson et bjson.so. La bibliothèque standard quickjs-libc.c est également un bon exemple de module natif.
Gestion de la mémoire
Utiliser JS_SetMemoryLimit() pour fixer une limite globale d'allocation de mémoire à un JSRuntime donné.
Des fonctions d'allocation de mémoire personnalisées peuvent être fournies avec JS_NewRuntime2().
La taille maximale de la pile du système peut être définie avec JS_SetMaxStackSize().
Délai d'exécution et interruptions
Utiliser JS_SetInterruptHandler() pour définir un callback qui est régulièrement appelé par le moteur lorsqu'il exécute du code. Ce callback peut être utilisé pour implémenter un timeout d'exécution.
Il est utilisé par l'interpréteur de ligne de commande pour implémenter un gestionnaire Ctrl-C.
Fonctionnement interne
Bytecode
Le compilateur génère le bytecode directement, sans représentation intermédiaire telle qu'un arbre d'analyse, ce qui le rend très rapide. Plusieurs passes d'optimisation sont effectuées sur le bytecode généré.
Un bytecode basé sur la pile a été choisi car il est simple et génère un code compact.
Pour chaque fonction, la taille maximale de la pile est calculée à la compilation, de sorte qu'aucun test de dépassement de pile n'est nécessaire à l'exécution.
Une table séparée de numéros de ligne compressés est maintenue pour les informations de débogage.
L'accès aux variables de fermeture est optimisé et presque aussi rapide que les variables locales.
L'évaluation directe en mode strict est optimisée.
Génération de l'exécutable
Compilateur qjsc
Le compilateur qjsc génère des sources C à partir de fichiers Javascript. Par défaut, les sources C sont compilées avec le compilateur du système (gcc ou clang).
La source C générée contient le bytecode des fonctions ou modules compilés. Si un exécutable complet est nécessaire, il contient également une fonction main() avec le code C nécessaire pour initialiser le moteur Javascript et pour charger et exécuter les fonctions et modules compilés.
Le code Javascript peut être mélangé avec des modules C.
Afin d'obtenir des exécutables plus petits, des fonctions Javascript spécifiques peuvent être désactivées, en particulier eval ou les expressions régulières. La suppression du code repose sur l'optimisation du temps de liaison du compilateur du système.
JSON binaire
qjsc fonctionne en compilant des scripts ou des modules et en les sérialisant dans un format binaire. Un sous-ensemble de ce format (sans fonctions ni modules) peut être utilisé comme JSON binaire. L'exemple test_bjson.js montre comment l'utiliser.
Attention : le format JSON binaire peut changer sans préavis, il ne doit donc pas être utilisé pour stocker des données persistantes. L'exemple test_bjson.js sert uniquement à tester les fonctions du format objet binaire.
Runtime
Chaînes de caractères
Les chaînes de caractères sont stockées sous la forme d'un tableau de caractères de 8 bits ou de 16 bits. L'accès aléatoire aux caractères est donc toujours rapide.
L'API C fournit des fonctions pour convertir les chaînes Javascript en chaînes C encodées UTF-8. Le cas le plus courant où la chaîne Javascript ne contient que des caractères ASCII n'implique aucune copie.
Objets
Les formes des objets (prototype de l'objet, noms des propriétés et drapeaux) sont partagées entre les objets afin d'économiser de la mémoire.
Les tableaux sans trous (sauf à la fin du tableau) sont optimisés.
Les accès aux tableaux typés sont optimisés.
Atomes
Les noms des propriétés des objets et certaines chaînes de caractères sont stockés sous forme d'atomes (chaînes de caractères uniques) afin d'économiser de la mémoire et de permettre une comparaison rapide. Les atomes sont représentés par des nombres entiers de 32 bits. La moitié de la plage d'atomes est réservée aux entiers littéraux immédiats de 0 à 2^{31}-1.
Nombres
Les nombres sont représentés soit par des entiers signés sur 32 bits, soit par des valeurs à virgule flottante IEEE-754 sur 64 bits. La plupart des opérations ont des chemins rapides pour les entiers de 32 bits.
Ramasse miettes
Le comptage de références est utilisé pour libérer les objets automatiquement et de manière déterministe. Une passe distincte d'élimination des cycles est effectuée lorsque la mémoire allouée devient trop importante. L'algorithme d'élimination des cycles n'utilise que le nombre de références et le contenu de l'objet, de sorte qu'il n'est pas nécessaire de manipuler les racines explicites du ramassage des ordures dans le code C.
JSValue
Il s'agit d'une valeur Javascript qui peut être un type primitif (tel que Nombre, Chaîne, ...) ou un Objet. La boîte NaN est utilisée dans la version 32 bits pour stocker les nombres à virgule flottante 64 bits. La représentation est optimisée pour que les entiers 32 bits et les valeurs comptées par référence puissent être testés efficacement.
Dans le code 64 bits, les JSValue ont une taille de 128 bits et aucune boîte NaN n'est utilisée. La raison en est que dans le code 64 bits, l'utilisation de la mémoire est moins critique.
Dans les deux cas (32 ou 64 bits), la valeur JSValue correspond exactement à deux registres du processeur, de sorte qu'elle peut être renvoyée efficacement par les fonctions C.
Appel de fonction
Le moteur est optimisé pour que les appels de fonction soient rapides. La pile du système contient les paramètres Javascript et les variables locales.
RegExp
Un moteur d'expressions régulières spécifique a été développé. Il est à la fois petit et efficace et prend en charge toutes les fonctionnalités d'ES2020, y compris les propriétés Unicode. Comme le compilateur Javascript, il génère directement du bytecode sans arbre d'analyse.
Le backtracking avec une pile explicite est utilisé afin qu'il n'y ait pas de récursion sur la pile du système. Les quantificateurs simples sont spécifiquement optimisés pour éviter les récursions.
Les récursions infinies provenant de quantificateurs avec des termes vides sont évitées.
La bibliothèque de regexp complète pèse environ 15 Ko (code x86), sans compter la bibliothèque Unicode.
Unicode
Une bibliothèque Unicode spécifique a été développée afin de ne pas dépendre d'une grande bibliothèque Unicode externe telle que ICU. Toutes les tables Unicode sont compressées tout en conservant une vitesse d'accès raisonnable.
La bibliothèque prend en charge la conversion des majuscules, la normalisation Unicode, les requêtes de script Unicode, les requêtes de catégorie générale Unicode et toutes les propriétés binaires Unicode.
La bibliothèque Unicode complète pèse environ 45 KiB (code x86).
BigInt, BigFloat, BigDecimal
BigInt, BigFloat et BigDecimal sont implémentés avec la bibliothèque libbf7. Elle pèse environ 90 Ko (code x86) et fournit des opérations en virgule flottante IEEE 754 de précision arbitraire et des fonctions transcendantes avec arrondi exact.
Licence
QuickJS est publié sous la licence MIT.
Sauf indication contraire, les sources de QuickJS sont la propriété de Fabrice Bellard et Charlie Gordon.
Notes personnelle
(1)
Comme je connais très peu JS, j'ai mis du temps avant de comprendre qu'il faut préfixer ces fonctions avec std.
Exemple
let fp = std.open("fob", "w");fp.printf("%s\n%s\n", "La main dans le fût", "les doigts entre les caisses");fp.close();let fob = std.loadFile("fob");
Après avoir enregistré le script ci-dessus sous le nom de fob.js
$ ls fob.*fob.jsmoa@zen /tmp/js$ qjs --std fob.jsmoa@zen /tmp/js$ cat fobLe doigt dans le fûtles mains entre les caisses