IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Sparkplug, un compilateur pour le moteur JavaScript V8, est publié avec la version 9.1 de V8,
Il apporte une diminution du temps de compilation

Le , par Bruno

149PARTAGES

9  0 
Les responsables de V8, le moteur JavaScript et WebAssembly open source de Google, ont annoncé Sparkplug, un compilateur JavaScript non optimisé. « Pour écrire un moteur JavaScript performant, il ne suffit pas de disposer d'un compilateur hautement optimisé comme TurboFan. En particulier pour les sessions de courte durée, comme le chargement de sites Web ou d'outils en ligne de commande, il y a beaucoup de travail qui se produit avant que le compilateur optimiseur ait même une chance de commencer à optimiser, sans parler du fait qu'il a le temps de générer le code optimisé, ont déclaré les responsables de V8. C'est là qu'intervient Sparkplug publié avec la version 9.1 de V8, qui se niche entre l'interpréteur Ignition et le compilateur optimisé TurboFan ».

V8 est le moteur JavaScript et WebAssembly open source de Google, écrit en C++. Il est utilisé dans Chrome et dans Node.js, entre autres. Il met en œuvre ECMAScript et WebAssembly et fonctionne sous Windows 7 ou ultérieur, macOS 10.12+ et les systèmes Linux qui utilisent des processeurs x64, IA-32, ARM ou MIPS. V8 peut fonctionner de manière autonome ou être intégré à toute application C++.

L'interpréteur de V8 est hautement optimisé et très rapide, mais les interpréteurs ont des frais généraux inhérents dont il est impossible de se débarrasser ; des choses comme les frais généraux de répartition qui font partie intrinsèque de la fonctionnalité d'un interpréteur. Avec le modèle actuel à deux compilateurs, il est impossible d’atteindre un code optimisé beaucoup plus rapidement ; l’équipe de V8 travaille à rendre l'optimisation plus rapide, mais à un certain point.


Un compilateur rapide

Sparkplug est conçu pour compiler rapidement. Selon V8, il y a quelques astuces qui rendent le compilateur Sparkplug rapide. Tout d'abord, il triche ; les fonctions qu'il compile ont déjà été compilées en bytecode, et le compilateur bytecode a déjà fait la majorité du travail difficile comme la résolution des variables, déterminer si les parenthèses sont en fait des fonctions flèches, déboguer les déclarations de déstructuration, et ainsi de suite. Sparkplug compile à partir du bytecode plutôt qu'à partir du code source JavaScript, et n'a donc pas à se soucier de tout cela. La deuxième astuce est que Sparkplug ne génère pas de représentation intermédiaire (IR) comme le font la plupart des compilateurs. Au lieu de cela, Sparkplug compile directement en code machine en un seul passage linéaire sur le bytecode, émettant un code qui correspond à l'exécution de ce bytecode. En fait, l'ensemble du compilateur est une instruction switch à l'intérieur d'une boucle for, qui envoie des fonctions fixes de génération de code machine par bytecode.

Code : Sélectionner tout
1
2
3
4
// The Sparkplug compiler (abridged).
for (; !iterator.done(); iterator.Advance()) {
  VisitSingleBytecode();
}
L'absence de RI signifie que le compilateur a des possibilités d'optimisation limitées, au-delà des optimisations très locales de type "peephole". Cela signifie également que le programmeur doit porter l'implémentation entière séparément pour chaque architecture supportée. Puisqu’il n'y a pas d'étape intermédiaire indépendante de l'architecture. Selon l’équipe V8, ce n'est pas un problème : « un compilateur rapide est un compilateur simple, donc le code est assez facile à porter ; et Sparkplug n'a pas besoin de faire une optimisation lourde, puisque nous avons de toute façon un excellent compilateur optimisant plus tard dans le pipeline ». Techniquement, ils effectuent actuellement deux passages sur le bytecode. Un pour découvrir les boucles, et un second pour générer le code réel.

Frames compatibles avec l'interpréteur


L'ajout d'un nouveau compilateur à une VM JavaScript existante est une tâche ardue. Il y a toutes sortes de choses que à prendre en charge au-delà de la simple exécution standard ; V8 a un débogueur, un profileur de CPU qui marche sur la pile, il y a des traces de pile pour les exceptions, l'intégration dans le tier-up, le remplacement sur la pile du code optimisé pour les boucles. Sparkplug fait un habile tour de passe-passe qui simplifie la plupart de ces problèmes, à savoir qu'il maintient des « frames de pile compatibles avec l'interpréteur ».

Notons que les frames de pile sont la façon dont l'exécution du code stocke l'état des fonctions ; chaque fois qu’une nouvelle fonction est appelée, un nouveau frame de pile est créé pour les variables locales de cette fonction. Un frame de pile est défini par un pointeur de frame (marquant son début) et un pointeur de pile (marquant sa fin).

Lorsqu'une fonction est appelée, l'adresse de retour est poussée sur la pile ; celle-ci est enlevée par la fonction lorsqu'elle revient, pour savoir où retourner. Ensuite, lorsque cette fonction crée un nouveau cadre, elle sauvegarde l'ancien pointeur de cadre sur la pile et place le nouveau pointeur de cadre au début de son propre cadre de pile. Ainsi, la pile possède une chaîne de pointeurs de cadre, chacun marquant le début d'un cadre qui pointe vers le cadre précédent.


Il s'agit de la disposition générale de la pile pour tous les types de fonctions ; il existe ensuite des conventions sur la façon dont les arguments sont passés, et sur la façon dont la fonction stocke les valeurs dans son cadre. Dans V8, il existe une convention pour les frames JavaScript que les arguments (y compris le récepteur) sont poussés dans l'ordre inverse sur la pile avant que la fonction soit appelée, et que les premiers emplacements sur la pile sont : la fonction actuelle appelée ; le contexte avec lequel elle est appelée ; et le nombre d'arguments qui ont été passés. Il s'agit de la disposition "standard" du frame JS.

Cette convention d'appel JS est partagée entre les trames optimisées et interprétées, et c'est ce qui permet, par exemple, de parcourir la pile avec une surcharge minimale lors du profilage du code dans le panneau de performance du débogueur.
Dans le cas de l'interpréteur Ignition, la convention est plus explicite. Ignition est un interprète basé sur des registres, ce qui signifie qu'il existe des registres virtuels (à ne pas confondre avec les registres de la machine !) qui stockent l'état actuel de l'interprète. Cela inclut les des fonctions locals JavaScript (déclarations var/let/const), et les valeurs temporaires. Ces registres sont stockés sur le cadre de la pile de l'interpréteur, avec un pointeur vers le tableau de bytecode en cours d'exécution, et le décalage du bytecode actuel dans ce tableau.


Sparkplug crée et maintient intentionnellement une disposition de trame qui correspond à celle de l'interprète ; chaque fois que l'interprète aurait stocké une valeur de registre, Sparkplug en stocke une aussi. Il agit ainsi pour plusieurs raisons :

  • cela simplifie la compilation de Sparkplug ; Sparkplug peut simplement refléter le comportement de l'interprète sans avoir à conserver une sorte de correspondance entre les registres de l'interprète et l'état de Sparkplug ;
  • cela accélère également la compilation, puisque le compilateur de bytecode a fait le travail difficile d'allocation de registre ;
  • il rend l'intégration avec le reste du système presque triviale ; le débogueur, le profileur, le déroulement de la pile des exceptions, l'impression de la trace de la pile, toutes ces opérations font des parcours de la pile pour découvrir quelle est la pile actuelle des fonctions en cours d'exécution, et toutes ces opérations continuent à travailler avec Sparkplug presque sans changement, parce qu'en ce qui les concerne, tout ce qu'ils ont est un cadre d'interprète ;
  • cela rend le remplacement sur la pile (OSR) trivial. L'OSR consiste à remplacer la fonction en cours d'exécution, actuellement, cela se produit lorsqu'une fonction interprétée se trouve à l'intérieur d'une boucle chaude (où elle s'élève vers le code optimisé pour cette boucle), et lorsque le code optimisé se désoptimise (où il s'abaisse et poursuit l'exécution de la fonction dans l'interpréteur). Avec les cadres de Sparkplug reflétant les cadres de l'interpréteur, toute logique OSR qui fonctionne pour l'interpréteur fonctionnera pour Sparkplug ; mieux encore, nous pouvons passer du code de l'interpréteur à celui de Sparkplug avec un surcoût de traduction de cadre presque nul.

Il y a un petit changement que nous faisons à la trame de la pile de l'interpréteur, qui est que nous ne gardons pas l'offset du bytecode à jour pendant l'exécution du code Sparkplug. Au lieu de cela, nous stockons une correspondance bidirectionnelle entre la plage d'adresses du code Sparkplug et l'offset du bytecode correspondant ; une correspondance relativement simple à coder, puisque le code Sparkplug est émis directement à partir d'une marche linéaire sur le bytecode. Chaque fois qu'un accès à la trame de la pile veut connaître le "bytecode offset" d'une trame Sparkplug, nous recherchons l'instruction en cours d'exécution dans ce mappage et renvoyons le bytecode offset correspondant. De même, chaque fois que nous voulons faire un OSR de l'interpréteur vers Sparkplug, nous pouvons rechercher l'offset de bytecode actuel dans le mappage, et sauter à l'instruction Sparkplug correspondante.

Source : L'équipe V8

Et vous ?

Quel est votre avis sur le sujet ?

Voir aussi :

Prisma : un ORM de nouvelle génération pour Node.js et TypeScript, pour concurrencer TypeORM et Sequelize et devenir la norme de l'industrie

StackBlitz annonce WebContainers, un outil qui permet de créer des environnements Node.js fullstack, il s'exécute dans un navigateur avec l'expérience d'édition de VS Code

TeaVM : un outil pour développer des applications Web rapides et modernes en Java, sans les difficultés d'une pile de développement JavaScript

Node.js 16 est maintenant disponible, et vient avec une mise à niveau du moteur JavaScript V8, des binaires préconstruits pour les puces Apple et des API stables supplémentaires

Une erreur dans cette actualité ? Signalez-nous-la !