Bidouilleux d'Web - Mot-clé - WASMLe blog technique de la communauté Mozilla francophone2022-02-04T14:07:43+01:00Communauté Mozilla francophoneurn:md5:935a9b6df47b29d6dc8c2ca47296b179DotclearLes types d'interfaçage WebAssembly : une interopérabilité pour les unir tousurn:md5:70acf5101d0285aabaf3f0bed08b33ac2019-09-02T12:30:00+02:002019-09-02T18:57:15+02:00sphinxGénéralInterfaceIRJavaScriptRustWASMWebAssembly<h2><em>Cet article est une traduction de <a href="https://hacks.mozilla.org/2019/08/webassembly-interface-types/">WebAssembly Interface Types: Interoperate with All the Things!</a>, écrit par <a href="http://twitter.com/linclark">Lin Clark</a>. Merci à elle pour le travail de rédaction originale !</em></h2>
<p>WebAssembly fait parler de lui, y compris en dehors du navigateur.
Cet engouement n’est pas seulement lié à un environnement d’exécution WebAssembly isolé mais aussi parce qu’on peut exécuter du code WebAssembly depuis des langages comme Python, Ruby ou Rust.</p>
<p>Pour quoi faire ? Voici quelques raisons :</p>
<ul>
<li><strong>Rendre les modules « natifs » moins compliqués</strong><br/>Les environnements d’exécution tels que Node ou CPython pour Python permettent également d’écrire des modules dans des langages bas niveau tels que C++. Une telle approche permet de profiter de la vitesse de ces langages bas niveaux. On peut ainsi utiliser des modules natifs en Node ou des modules d’extension en Python. Toutefois ces modules sont souvent difficiles à utiliser car ils doivent être compilés sur l’appareil de l’utilisateur. Avec un module « natif » WebAssembly, on obtient une bonne partie de cette vitesse sans compliquer la mise en œuvre.</li>
<li><strong>Isoler plus facilement le code natif dans des bacs à sable</strong><br/>D’un autre côté, pour des langages bas niveau tels que Rust, pas besoin d’utiliser WebAssembly pour gagner de la vitesse. En revanche, cela peut servir pour la sécurité. Comme nous en parlions lors de <a href="https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/">l’annonce de WASI</a>, WebAssembly fournit un bac à sable léger par défaut et un langage comme Rust pourrait utiliser WebAssembly afin de placer ses modules natifs dans un bac à sable.</li>
<li><strong>Partager du code natif à travers différentes plateformes</strong><br/>Les développeurs peuvent s’épargner du temps et des coûts de maintenance s’ils peuvent réutiliser la même base de code sur différentes plateformes (entre une application web et une application pour le bureau par exemple). Cela concerne aussi bien les langages de script que les langages bas niveaux. De plus, WebAssembly apporte une solution sans ralentir quoi que ce soit sur les plateformes en question.</li>
</ul>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/01-01-why.png" title="4 personnages représentant Python, Ruby, Rust et C++ disent : Nous aimons la vitesse de WebAssembly, la sécurité qu'il pourrait nous apporter et nous souhaitons tous que les développeurs puissent travailler efficacement"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.01-01-why_m.png" alt="4 personnages représentant Python, Ruby, Rust et C++ disent : Nous aimons la vitesse de WebAssembly, la sécurité qu'il pourrait nous apporter et nous souhaitons tous que les développeurs puissent travailler efficacement" style="margin: 0 auto; display: block;" /></a></p>
<p>WebAssembly pourrait donc aider d’autres langages à résoudre des problèmes majeurs.</p>
<p>Malgré cela, convertir une valeur d’un type vers l’autre est possible en suivant certaines règles cette façon. WebAssembly peut être exécuté dans ces environnements mais ce n’est pas suffisant.</p>
<p>Aujourd’hui, WebAssembly ne dialogue avec l’extérieur qu’avec des nombres et ses fonctions peuvent être appelées depuis un autre langage et vice versa.</p>
<p>Mais si une fonction prend des arguments ou renvoie une valeur qui ne sont pas des nombres, ça devient vite compliqué. On peut alors :</p>
<ul>
<li>Mettre à disposition un module dont l’API est ultra-compliqué et ne manipule que des nombres : tant pis pour l’utilisateur du module…</li>
<li>Ajouter du code intermédiaire (de la « <em>glue</em> ») pour chaque environnement dans lequel on souhaite que ce module puisse être exécuté : tant pis pour le développeur du module.</li>
</ul>
<p>Faut-il s’en satisfaire ?</p>
<p>On devrait pouvoir fournir un seul module WebAssembly qui puisse être exécuté n’importe où… sans pour autant compliquer la vie de l’utilisateur du module ou de son développeur.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/01-02-user-and-dev.png" title="D'un côté un utilisateur de module qui se demande : Qu'est-ce que c'est encore que cette API ? et d'un autre côté, un développeur qui dit Pff, encore tout un tas de glue à créer pour que ça fonctionne. En dessous, les deux disent simplement : Attendez, ça fonctionne ??"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.01-02-user-and-dev_m.png" alt="D'un côté un utilisateur de module qui se demande : Qu'est-ce que c'est encore que cette API ? et d'un autre côté, un développeur qui dit Pff, encore tout un tas de glue à créer pour que ça fonctionne. En dessous, les deux disent simplement : Attendez, ça fonctionne ??" style="margin: 0 auto; display: block;" /></a></p>
<p>Le même module WebAssembly pourrait utiliser des API riches et des types complexes afin de dialoguer avec :</p>
<ul>
<li>Des modules s’exécutant dans leur environnement natif (ex. des modules Python s’exéutant dans un environnement Python)</li>
<li>D’autres modules WebAssembly écrits depuis d’autres langages sources (ex. un module Rust et un module Go s’exécutant de concert dans le navigateur)</li>
<li>Le système sous-jacent (ex. un module WASI fournissant une interface système avec le système d’exploitation ou avec les API du navigateur).</li>
</ul>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/01-03-star-diagram.png" title="Un diagramme avec un fichier .wasm qui dialogue avec : des modules qui s'exécutent dans leurs environnements (ex. PHP, Ruby, Python) ; des modules écrits depuis d'autres langages sources (ex. Rust, Go) ; des systèmes hôtes qui fonctionnent directement avec le système d'exploitation. En haut : le slogan : Les types d'interfaçage WebAssembly : l'interopérabilité pour tous."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.01-03-star-diagram_m.png" alt="Un diagramme avec un fichier .wasm qui dialogue avec : des modules qui s'exécutent dans leurs environnements (ex. PHP, Ruby, Python) ; des modules écrits depuis d'autres langages sources (ex. Rust, Go) ; des systèmes hôtes qui fonctionnent directement avec le système d'exploitation. En haut : le slogan : Les types d'interfaçage WebAssembly : l'interopérabilité pour tous." style="margin: 0 auto; display: block;" /></a></p>
<p>Avec une nouvelle proposition, nous pouvons voir comment cela peut fonctionner (et ça fonctionne :)). Voici par exemple une démo :</p>
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/Qn_4F3foB3Q" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<p>Voyons comment cela fonctionne. Mais avant regardons la situation actuelle et les problèmes que nous essayons de résoudre.</p>
<h2>Discussion entre WebAssembly et JavaScript</h2>
<p>WebAssembly ne se limite pas au Web mais jusqu’à présent, une grande partie du développement de WebAssembly concernait le Web.</p>
<p>En effet, on conçoit mieux lorsqu’on se concentre sur la résolution de problèmes concrets. Ce langage devait être exécuté sur le Web et c’était donc un point de départ pertinent.</p>
<p>On a ainsi obtenu un produit minimum viable (MVP) avec un périmètre bien défini. WebAssembly devait alors seulement être capable de dialoguer avec un autre langage : JavaScript.</p>
<p>Ce fut relativement facile à obtenir. Au sein du navigateur, WebAssembly et JS s’exécutent dans le même moteur et le moteur peut donc <a href="https://hacks.mozilla.org/2018/10/calls-between-javascript-and-webassembly-are-finally-fast-%f0%9f%8e%89/">les aider à discuter efficacement</a>.
<a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-01-js-interop-01.png" title="À droite, un bonhomme-fichier JS qui demande à un intermédiaire : Peux-tu demander au wasm de générer les pixels d'une image pour moi ?. L'intermédiaire répond : Sans problème. À gauche le bonhomme-fichier WASM attend."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-01-js-interop-01_m.png" alt="À droite, un bonhomme-fichier JS qui demande à un intermédiaire : Peux-tu demander au wasm de générer les pixels d'une image pour moi ?. L'intermédiaire répond : Sans problème. À gauche le bonhomme-fichier WASM attend." style="margin: 0 auto; display: block;" /></a></p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-01-js-interop-02.png" title="L'intermédiaire au centre demande au fichier-bonhomme WASM : j'aimerais que tu exécutes imageGenerate."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-01-js-interop-02_m.png" alt="L'intermédiaire au centre demande au fichier-bonhomme WASM : j'aimerais que tu exécutes imageGenerate." style="margin: 0 auto; display: block;" /></a></p>
<p>Malgré tout, il y a un problème lorsque ces deux-là essaient de dialoguer : ils utilisent des types différents.</p>
<p>Actuellement, WebAssembly ne s’exprime qu’avec des nombres. JavaScript sait ce qu’est un nombre mais possède également quelques autres types.</p>
<p>Et même les nombres ne sont pas vraimeent les mêmes. WebAssembly possède quatre types de nombres : <code>int32</code>, <code>int64</code>, <code>float32</code>, <code>float64</code>. JavaScript possède quant à lui un seul type <code>Number</code> (<a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/BigInt/">BigInt</a> sera bientôt un nouveau type numérique en JS).</p>
<p>La différence entre ces types ne s’arrête pas aux noms. Les valeurs sont aussi stockées différemment en mémoire.</p>
<p>Pour commencer, n’importe quelle valeur JavaScript (quel que soit son type) est placée dans une boîte (voir <a href="https://hacks.mozilla.org/2018/10/calls-between-javascript-and-webassembly-are-finally-fast-%f0%9f%8e%89/#js-to-wasm">ce précédent article</a> où j’expliquais le concept).</p>
<p>En revanche, WebAssembly utilise des types statiques pour les nombres et il n’utilise ni ne comprend les boîtes de JavaScript.</p>
<p>Cette différence rend le dialogue un peu compliqué.
<a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-03-number-mismatch.png" title="L'intermédiaire au milieu demande au fichier WASM : 'le fichier JS voudrait que tu ajoutes 5 et 7'. Le module WASM répond : 'Aucun problème, ça fait 9.2368828e
+18'. L'intermédiaire rétorque : 'Pardon ?!'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-03-number-mismatch_m.png" alt="L'intermédiaire au milieu demande au fichier WASM : 'le fichier JS voudrait que tu ajoutes 5 et 7'. Le module WASM répond : 'Aucun problème, ça fait 9.2368828e
+18'. L'intermédiaire rétorque : 'Pardon ?!'" style="margin: 0 auto; display: block;" /></a>
Malgré cela, convertir une valeur d’un type vers l’autre est possible en suivant quelques règles simples.</p>
<p>Les règles simples sont facilement écrites et on peut les retrouver dans <a href="https://www.w3.org/TR/wasm-js-api/#tojsvalue">la spécification de l’API entre WebAssembly et JavaScript</a>.
<a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-04-mapping-book.png" title="Un livre ouvert où la page de gauche contient 'WASM vers JS' et où la page de droite contient 'int32 -> Number', 'int64 -> BigInt', 'float32 -> Number' et 'float64 -> Number'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-04-mapping-book_m.png" alt="Un livre ouvert où la page de gauche contient 'WASM vers JS' et où la page de droite contient 'int32 -> Number', 'int64 -> BigInt', 'float32 -> Number' et 'float64 -> Number'" style="margin: 0 auto; display: block;" /></a></p>
<p>Cette correspondance est inscrite dans les moteurs d’exécution.</p>
<p>C’est un peu comme si le moteur possédait un manuel. Lorsque le moteur doit passer des paramètres ou des valeurs de retour entre JavaScript et WebAssembly, il sort le manuel et le consulte afin de savoir comment convertir ces valeurs.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-05-number-conversion.png" title="Le fichier JS demande à l'intermédiaire 'Peux-tu demander au WASM d'ajouter 5 et 7 ?' puis l'intermédiaire réfléchit 'Alors il faut que je convertisse le nombre 5 de type Number en un int32, pour ça il va falloir que...'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-05-number-conversion_m.png" alt="02-05-number-conversion.png, sept. 2019" style="margin: 0 auto; display: block;" /></a></p>
<p>Avoir aussi peu de types à gérer (uniquement des nombres) rend la chose facile. Ce fut une bonne chose pour un MVP et ça a réduit le nombre de questions difficiles à trancher.</p>
<p>En contrepartie, ce fut plus compliqué pour les développeurs d’utiliser WebAssembly. Pour passer des chaînes de caractères entre JavaScript et WebAssembly, il a fallu trouver une méthode pour transformer des chaînes de caractères en tableaux de nombres puis de faire l’opération inverse. Nous avions couvert sur ce sujet dans <a href="https://hacks.mozilla.org/2018/03/making-webassembly-better-for-rust-for-all-languages/">un précédent billet</a>.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/04_wasm_bindgen_02.png" title="Un diagramme montrant comment la chaîne de caractères 'Hello' JavaScript est convertie en nombres puis placée dans un objet mémoire pour WebAssembly"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.04_wasm_bindgen_02_m.png" alt="Un diagramme montrant comment la chaîne de caractères 'Hello' JavaScript est convertie en nombres puis placée dans un objet mémoire pour WebAssembly" style="margin: 0 auto; display: block;" /></a></p>
<p>Ce n’est pas difficile mais c’est laborieux. Des outils ont naturellement été construits afin de rendre cette conversion transparente.</p>
<p>Entre autres, on pourra trouver des outils tels que <a href="https://rustwasm.github.io/docs/wasm-bindgen/">wasm-bindgen (en Rust)</a> et <a href="https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#embind">Embind d’Emscripten</a> qui enveloppent automatiquement le module WebAssembly avec du code JavaScript de liaison qui s’occupe de la traduction des chaînes de caractères en nombres.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-07-js-glue.png" title="Un nouveau fichier fait son apparition entre le fichier WASM et le fichier JS. Le fichier JS tout à droite dit 'Zut, je vais encore devoir passer une chaîne de caractères au WASM...'. Là le fichier JS pour le code de liaison intervient et dit 'Je peux aider, je vais placer la chaîne en mémoire et indiquer au module WASM son emplacement'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-07-js-glue_m.png" alt="Un nouveau fichier fait son apparition entre le fichier WASM et le fichier JS. Le fichier JS tout à droite dit 'Zut, je vais encore devoir passer une chaîne de caractères au WASM...'. Là le fichier JS pour le code de liaison intervient et dit 'Je peux aider, je vais placer la chaîne en mémoire et indiquer au module WASM son emplacement'" style="margin: 0 auto; display: block;" /></a>
Ces outils ont également permis d’effectuer des transformations pour des types de plus haut niveau comme des objets complexes avec des propriétés.</p>
<p>Cela fonctionne mais pour certains cas triviaux, ce n’est pas suffisant.</p>
<p>Imaginons qu’on veuille passer une chaîne de caractères entre deux scripts JS via un module WebAssembly. On doit avoir une fonction JavaScript qui passe une chaîne à une fonction WebAssembly puis le module WebAssembly doit passer cette chaîne à une autre fonction JavaScript.</p>
<p>Pour que tout cela fonctionne, il faut :</p>
<ol>
<li>Que la première fonction JavaScript passe la chaîne de caractères au code JS qui s’occupe de la liaison (“glue code”)</li>
<li>Que le code de liaison transforme cette chaîne de caractères en nombre et passe ces nombres en mémoire linéaire</li>
<li>Qu’il envoie un nombre (le pointeur vers le début de la zone mémoire) au module WebAssembly</li>
<li>Que la fonction WebAssembly passe ce nombre au code de liaison JS de l’autre côté</li>
<li>Que le code de liaison JavaScript retire ces nombres de la mémoire linéaire pour les décoder en chaîne de caractères</li>
<li>Que le deuxième script de liaison fournisse cette chaîne à la deuxième fonction JS.</li>
</ol>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-08-encode-decode-01.png" title="Première étape : le fichier JS tout à gauche passe la chaîne 'Hello' au fichier JS de liaison à sa droite."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-08-encode-decode-01_m.png" alt="Première étape : le fichier JS tout à gauche passe la chaîne 'Hello' au fichier JS de liaison à sa droite." style="margin: 0 auto; display: block;" /></a></p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-08-encode-decode-02.png" title="Deuxième étape, ce fichier avec le code de liaison fait le nécessaire pour convertir la chaîne en nombres en mémoire."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-08-encode-decode-02_m.png" alt="Deuxième étape, ce fichier avec le code de liaison fait le nécessaire pour convertir la chaîne en nombres en mémoire." style="margin: 0 auto; display: block;" /></a></p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-08-encode-decode-03.png" title="Troisième étape, le fichier avec le code liaison indique à l'intermédiaire : 'Veuillez envoyer l'index 2 au WASM'. L'intermédiaire réfléchit alors 'OK pour passer d'une valeur JSValue à un int32, je dois...'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-08-encode-decode-03_m.png" alt="Troisième étape, le fichier avec le code liaison indique à l'intermédiaire : 'Veuillez envoyer l'index 2 au WASM'. L'intermédiaire réfléchit alors 'OK pour passer d'une valeur JSValue à un int32, je dois...'" style="margin: 0 auto; display: block;" /></a></p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-08-encode-decode-04.png" title="Quatrième étape, le fichier WASM demande à l'intermédiaire : 'Peux-tu passer l'index 2 ?' pour le script JS à sa droite. L'intermédiaire réfléchit 'Alors pour passer d'un int32 à un Number, je dois...'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-08-encode-decode-04_m.png" alt="Quatrième étape, le fichier WASM demande à l'intermédiaire : 'Peux-tu passer l'index 2 ?' pour le script JS à sa droite. L'intermédiaire réfléchit 'Alors pour passer d'un int32 à un Number, je dois...'" style="margin: 0 auto; display: block;" /></a></p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-08-encode-decode-05.png" title="Cinquième étape, le deuxième fichier de glue JS (situé à droite du WASM) calcule pour convertir le tableau mémoire en chaîne de caractères. Il produit la valeur 'Hello'."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-08-encode-decode-05_m.png" alt="Cinquième étape, le deuxième fichier de glue JS (situé à droite du WASM) calcule pour convertir le tableau mémoire en chaîne de caractères. Il produit la valeur 'Hello'." style="margin: 0 auto; display: block;" /></a></p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-08-encode-decode-06.png" title="Sixième étape, le fichier de glue JS passe la valeur 'Hello' au script JS situé à sa droite"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-08-encode-decode-06_m.png" alt="Sixième étape, le fichier de glue JS passe la valeur 'Hello' au script JS situé à sa droite" style="margin: 0 auto; display: block;" /></a></p>
<p>On a donc un code de liaison JS qui effectue “simplement” l’opération inverse de celle effectuée plus tôt pour la conversion. Cela fait beaucoup de travail pour en arriver là.</p>
<p>Si la chaîne de caractères pouvait directement être passée au module WebAssembly sans toutes ces transformations, ce serait bien plus simple.</p>
<p>WebAssembly ne pourrait pas manipuler cette valeur, il ne connaît pas ce type : on ne résout pas ce problème de compréhension.</p>
<p>Mais si on pouvait simplement passer la valeur au module WebAssembly comme un passe-plat, cela suffirait aux deux fonctions JavaScript, car elles savent quoi faire avec une valeur d’un tel type.</p>
<p>Il s’agit ici d’une raison de <a href="https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md#language-extensions">la proposition pour les types de référence WebAssembly</a>. Cette proposition ajoute un nouveau type de base à WebAssembly intitulé <code>anyref</code>.</p>
<p>Avec une valeur <code>anyref</code>, un script JS fournirait au WebAssembly une référence objet (en fait un pointeur qui ne révèle pas l’adresse mémoire). Cette référence pointera vers l’objet sur le tas JS. Le module WebAssembly pourrait alors passer cette valeur à d’autres fonctions JS qui sauraient l’utiliser.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-09-anyref-01.png" title="Première étape. Le fichier JS tout à gauche tend la valeur 'Hello' demande à l'intermédiaire 'Peux-tu passer cette chaîne de caractères au WASM ?'. L'intermédiaire réfléchit 'Hmm on dirait qu'il suffit de founir un pointeur au WASM, facile'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-09-anyref-01_m.png" alt="Première étape. Le fichier JS tout à gauche tend la valeur 'Hello' demande à l'intermédiaire 'Peux-tu passer cette chaîne de caractères au WASM ?'. L'intermédiaire réfléchit 'Hmm on dirait qu'il suffit de founir un pointeur au WASM, facile'" style="margin: 0 auto; display: block;" /></a></p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-09-anyref-02.png" title="Deuxième étape. Le module WASM au centre, tendant la valeur 'Hello', demande à l'intermédiaire 'Peux-tu passer ça au module JavaScript ? Je ne sais pas ce que c'est mais le JS devrait comprendre'. L'intermédiaire réfléchit 'C'est déjà un pointeur vers un objet d'un tas de mémoire JS, plutôt simple'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-09-anyref-02_m.png" alt="Deuxième étape. Le module WASM au centre, tendant la valeur 'Hello', demande à l'intermédiaire 'Peux-tu passer ça au module JavaScript ? Je ne sais pas ce que c'est mais le JS devrait comprendre'. L'intermédiaire réfléchit 'C'est déjà un pointeur vers un objet d'un tas de mémoire JS, plutôt simple'" style="margin: 0 auto; display: block;" /></a></p>
<p>Cela résout un problème d’interopérabilité avec JavaScript, mais il en existe d’autres dans le navigateur.</p>
<p>Un navigateur possède un ensemble beaucoup plus large de types et WebAssembly doit être capable d’inter-opérer avec ces types si on veut que les performances soient décentes.</p>
<h2>Discussion directe entre WebAssembly et le navigateur</h2>
<p>JavaScript ne représente qu’une partie du navigateur. Ce dernier possède de nombreuses autres fonctions qu’on peut utiliser : les API Web.</p>
<p>Sous le capot, les fonctions de ces API Web sont généralement écrites en C++ ou en Rust. Ces deux langages stockent chacun à leur façon les objets en mémoires.</p>
<p>Les paramètres et valeurs de retour de ces API Web sont décrites par de nombreux types. Il sera fastidieux de décrire des conversions pour chacun de ces types. Pour simplifier les choses, il existe un standard pour la structure de ces types : <a href="https://developer.mozilla.org/docs/Mozilla/WebIDL_bindings">Web IDL</a>.</p>
<p>Lorsque vous utilisez ces fonctions, c’est généralement depuis du code JavaScript. Cela signifie que vous passez des valeurs exprimées sur des types JavaScript. Comment un type JavaScript se retrouve converti en type Web IDL ?</p>
<p>À l’instar des correspondances établies entre les types WebAssembly et les types JavaScript, il existe des correspondances entre les types JavaScript et Web IDL.</p>
<p>Là encore, on peut voir cela comme un autre manuel qui explique comment passer de Web IDL à JavaScript. Là aussi ces correspondances font partie intégrant du moteur du navigateur.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-02-mapping-book.png" title="Un livre ouvert où la page de gauche contient 'JS vers Web IDL' et où la page de droite contient 'String -> DOMString', 'String -> ByteString', 'String -> USVString', 'Object -> object'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-02-mapping-book_m.png" alt="Un livre ouvert où la page de gauche contient 'JS vers Web IDL' et où la page de droite contient 'String -> DOMString', 'String -> ByteString', 'String -> USVString', 'Object -> object'" style="margin: 0 auto; display: block;" /></a></p>
<p>Pour la plupart des types, la correspondance entre JavaScript et Web IDL est assez simple. Ainsi, un type tel que <code>DOMString</code> est compatible avec le type JS <code>String</code> car les deux ont une correspondance directe.</p>
<p>Que se passe-t-il lorsqu’on essaie d’appeler une API Web depuis du code WebAssembly ?
Il y a un problème.</p>
<p>À l’heure actuelle, il n’existe pas de correspondance entre les types WebAssembly et les types Web IDL. Cela signifie que même pour les types simples comme les nombres, l’appel doit passer par JavaScript.</p>
<p>Voici ce qui se produit :</p>
<ol>
<li>WebAssembly passe la valeur au JavaScript</li>
<li>Pour ce faire, le moteur convertit la valeur en un type JavaScript et la place sur le tas de la mémoire JavaScript</li>
<li>La valeur JavaScript est ensuite passée à la fonction de la Web API. Ici, le moteur convertit la valeur JS en un type Web IDL et la place sur une autre zone mémoire, le tas du <code>renderer</code>.</li>
</ol>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-03-wasm-to-browser-01.png" title="Première étape, le module WASM à gauche tend la valeur 38."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-03-wasm-to-browser-01_m.png" alt="Première étape, le module WASM à gauche tend la valeur 38." style="margin: 0 auto; display: block;" /></a></p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-03-wasm-to-browser-02.png" title="Deuxième étape, l'intermédiaire réfléchit 'Pour commencer, on passe du type int32 au type Number et on place la valeur sur le tas de la mémoire pour JS'. La valeur 38 est ajoutée au tas de la mémoire."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-03-wasm-to-browser-02_m.png" alt="Deuxième étape, l'intermédiaire réfléchit 'Pour commencer, on passe du type int32 au type Number et on place la valeur sur le tas de la mémoire pour JS'. La valeur 38 est ajoutée au tas de la mémoire." style="margin: 0 auto; display: block;" /></a></p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-03-wasm-to-browser-03.png" title="Troisième étape, l'intermédiaire réfléchit 'et ensuite on convertit ça en double... et je peux enfin exécuter la fonction'. La valeur 38 est ajoutée au tas du renderer avec le type double."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-03-wasm-to-browser-03_m.png" alt="Troisième étape, l'intermédiaire réfléchit 'et ensuite on convertit ça en double... et je peux enfin exécuter la fonction'. La valeur 38 est ajoutée au tas du renderer avec le type double." style="margin: 0 auto; display: block;" /></a></p>
<p>Ce n’est pas optimal : plus de tâches à effectuer et plus de mémoire consommée.</p>
<p>Une solution a priori évidente consisterait à créer des correspondances entre WebAssembly et Web IDL. Toutefois, ce n’est pas aussi trivial qu’il y paraît.</p>
<p>Pour les types Web IDL simples tels que <code>boolean</code>et <code>unsigned long</code> (un nombre), il existe des correspondances évidentes entre WebAssembly et Web IDL.</p>
<p>Mais une bonne partie des paramètres utilisées par les API Web ont des types complexes. Une API peut, par exemple, prendre un dictionnaire (comme un objet avec des propriétés) ou une série (un tableau) en entrée.</p>
<p>Pour créer une correspondance directe entre les types WebAssembly et les types Web IDL, il faudrait ajouter des types de plus haut niveau. C’est ce que nous faisons avec <a href="https://github.com/WebAssembly/gc">la proposition d’ajout d’un ramasse-miettes à WebAssembly</a>. Grâce à ceci, les modules WebAssembly pourront créer des objets pour le ramasse-miettes tels que des structures et des tableaux qui pourront servir aux correspondances pour les types Web IDL.</p>
<p>Mais si la seule façon d’interagir avec les API Web consiste à utiliser les objets du ramasse-miettes, cela complique la tâche pour les langages tels que Rust et C++ qui n’utilisent pas les objets du ramasse-miettes en temps normal. À chaque interaction avec une API Web, il faudrait créer un objet du ramasse-miettes et copier les valeurs depuis la mémoire linéaire dans l’objet.</p>
<p>Le résultat ainsi obtenu est légèrement mieux que la situation actuelle avec le code de liaison JavaScript.</p>
<p>On ne souhaite pas avoir de code de liaison JavaScript pour construire les objets du ramasse-miettes : c’est un gaspillage de temps et de ressources. Réciproquement, on ne veut pas que le module WebAssembly construise ces objets pour les mêmes raisons.</p>
<p>On souhaite qu’appeler les API Web soit aussi simple pour les langages qui utilisent une mémoire linéaire (tels que Rust ou C++) que pour les langages qui utilisent un ramasse-miettes intégré. Il faut donc également une méthode pour créer une correspondance entre les objets en mémoire linéaire et les types Web IDL.</p>
<p>Mais il y a un hic. Chaque langage représente des choses en mémoire linéaire de façon différente. On ne peut pas choisir une de ces représentations spécifiquement, tous les autres langages en pâtiraient.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-07-picking-lang.png" title="Un bonhomme au centre, entourés de langages comme C++, Kotlin, Go, D, Rust, Haskell et qui s'exclame : 'Je choisis... celui-là' en pointant Rust. Une annotation rouge indique avec une flèche 'Mauvaise idée'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-07-picking-lang_m.png" alt="Un bonhomme au centre, entourés de langages comme C++, Kotlin, Go, D, Rust, Haskell et qui s'exclame : 'Je choisis... celui-là' en pointant Rust. Une annotation rouge indique avec une flèche 'Mauvaise idée'" style="margin: 0 auto; display: block;" /></a></p>
<p>Bien que l’organisation mémoire soit différente, il y a certains concepts abstraits qui sont généralement partagés.</p>
<p>Ainsi, pour les chaînes de caractères, un langage possède souvent un pointeur vers le début de la chaîne de caractères et sa longueur. Si la chaîne de caractères possède une représentation plus complexe, il est généralement utile de convertir les chaînes vers ce format pour appeler des API externes.</p>
<p>De cette façon, on peut réduire la chaîne en un type que WebAssembly comprend : deux valeurs <code>i32</code>.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-08-types-wasm-understands.png" title="La chaîne de caractères 'Hello' encodée en mémoire et deux valeurs 'offset=2' et 'length=5' avec deux flèches vers elles et l'indication 'des types que WebAssembly comprend'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-08-types-wasm-understands_m.png" alt="La chaîne de caractères 'Hello' encodée en mémoire et deux valeurs 'offset=2' et 'length=5' avec deux flèches vers elles et l'indication 'des types que WebAssembly comprend'" style="margin: 0 auto; display: block;" /></a></p>
<p>Là encore, un petit hic. WebAssembly est un langage fortement typé. Pour des raions de <a href="https://webassembly.org/docs/security/">sécurité</a>, le moteur vérifie que le code appelant passe des valeurs dont les types correspondent à ceux attendus par l’appelé.</p>
<p>Cela empêche les attaquants d’exploiter des incohérences de type pour détourner le moteur.</p>
<p>Si vous appelez une fonction qui utilise une chaîne de caractère et que vous tentez de lui passer un entier, le moteur vous criera dessus. Et ça tombe bien, c’est ce qu’il <em>devrait</em> faire.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-09-type-mismatch.png" title="Le module WASM tend un entier (57) et dit au moteur 'tiens, voilà un entier, utilise le avec une fonction qui prend une chaîne en entrée'. Le moteur rétorque : 'tu essaies quoi au juste ? de permettre aux attaquants de me pirater moi ?!'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-09-type-mismatch_m.png" alt="Le module WASM tend un entier (57) et dit au moteur 'tiens, voilà un entier, utilise le avec une fonction qui prend une chaîne en entrée'. Le moteur rétorque : 'tu essaies quoi au juste ? de permettre aux attaquants de me pirater moi ?!'" style="margin: 0 auto; display: block;" /></a></p>
<p>Il nous faut donc une façon pour un module de dire au moteur quelque chose comme “Je sais que <code>Document.createElement()</code> prend une chaîne de caractères, mais je vais l’appeler et vous envoyer deux entiers. Prenez ces deux entiers pour créer un objet <code>DOMString</code> à partir des données en mémoire linéaire. Le premier entier sera l’adresse de départ de la chaîne de caractères et le second correspondra à sa longueur.”</p>
<p>C’est tout l’objectif de la proposition pour les types d’interfaçage Web IDL. On fournit à un module WebAssembly une façon d’indiquer une correspondance entre les types qu’il utilise et les types Web IDL.</p>
<p>Ces correspondances ne sont pas enregistrées en dur dans le moteur. C’est le module qui fournit un petit livret expliquant les correspondances qu’il utilise.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-10-booklet.png" title="Le module WASM s'incline pour tendre un livret au moteur et lui dit 'Voici quelques notes qui t'indiqueront comment traduire mes types en types d'interfaçage et vice versa'."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-10-booklet_m.png" alt="Le module WASM s'incline pour tendre un livret au moteur et lui dit 'Voici quelques notes qui t'indiqueront comment traduire mes types en types d'interfaçage et vice versa'." style="margin: 0 auto; display: block;" /></a></p>
<p>Le moteur a donc une méthode pour dire “pour cette fonction, la vérification des types pour les chaînes de caractères consistera à vérifier deux entiers”.</p>
<p>Le couplage entre le module et ce livret d’explication est aussi utile pour une autre raison.</p>
<p>Parfois, un module qui stocke normalement ses chaînes en mémoire linéaire pourra vouloir utilise une <code>anyref</code> ou un type du ramasse-miettes pour un cas spécifique. C’est le cas notamment pour un module qui passe un objet qu’il a obtenu d’une fonction JavaScript (un nœud du DOM par exemple) vers une API Web.</p>
<p>Ainsi, un module doit pouvoir choisir au cas par cas entre les fonctions (voire entre les arguments) la façon dont la correspondance de type est gérée. La correspondance étant fournie par le module, ce dernier peut décrire une correspondance sur-mesure.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-11-granularity.png" title="Le module WASM indique au moteur : 'Attention à bien lire. Pour certaines fonctions qui utilisent des DOMString, je te fournirai deux nombres mais pour les autres je t'enverrai simplement la valeur DOMString que m'a fourni le JavaScript."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-11-granularity_m.png" alt="Le module WASM indique au moteur : 'Attention à bien lire. Pour certaines fonctions qui utilisent des DOMString, je te fournirai deux nombres mais pour les autres je t'enverrai simplement la valeur DOMString que m'a fourni le JavaScript." style="margin: 0 auto; display: block;" /></a></p>
<p>Comment faire pour générer ce livret ?</p>
<p>Le compilateur prend en charge cette opération. Il ajoute une section spécifique au module WebAssembly. Pour la plupart des chaînes de compilation des différents langages, le développeur n’aura pas un grand travail supplémentaire.</p>
<p>Prenons un exemple avec la chaîne de compilation Rust et comment celle-ci gère le passage d’une chaîne de caractères à la fonction <code>alert</code>.</p>
<pre><code>#[wasm_bindgen]
extern "C" {
fn alert(s: &str);
}
</code></pre>
<p>Le développeur doit juste indiquer au compilateur d’ajouter cette fonction au livret avec l’annotation <code>#[wasm_bindgen]</code>. Par défaut, le compilateur considèrera qu’il s’agit d’une chaîne de caractères représentée en mémoire linéaire et ajoutera la bonne correspondance. Si on avait souhaité la gérer différemment (comme un <code>anyref</code> par exemple), on aurait écrit une autre annotation à destination du compilateur.</p>
<p>Grâce à ça, on peut enlever le code JavaScript intermédiaire pour la liaison. Le passage de valeur entre WebAssembly et les API Web est plus rapide. De plus, cela fait moins de JavaScript à distribuer.</p>
<p>Au passage, aucun compromis n’a été effectué quant aux langages pris en charges. On peut utiliser n’importe quel langage qui compile vers WebAssembly. Tous ces langages peuvent définir leur correspondance vers les types Web IDL, peu importe qu’ils utilisent une mémoire linéaire, des objets de ramasse-miettes ou les deux.</p>
<p>En prenant un peu de recul sur cette solution, on peut voir qu’elle résout un bien plus grand problème.</p>
<h2>WebAssembly : un langage pour tous leur parler</h2>
<p>Revenons à la promesse que nous évoquions au début de ce billet.</p>
<p>Existe-t-il une méthode réaliste afin que WebAssembly puisse parler à ces différents systèmes quels que soient les types qu’ils utilisent ?</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/04-01-star-diagram.png" title="Un diagramme avec un module WASM à droite et trois doubles flèches qui partent vers : des modules PHP/Python/Ruby qui s'exécutent dans leurs environnements ; des modules Rust/Go qui sont écrits avec un autre langage ; des systèmes hôtes qui fonctionnent directement avec le système d'exploitation"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.04-01-star-diagram_m.png" alt="Un diagramme avec un module WASM à droite et trois doubles flèches qui partent vers : des modules PHP/Python/Ruby qui s'exécutent dans leurs environnements ; des modules Rust/Go qui sont écrits avec un autre langage ; des systèmes hôtes qui fonctionnent directement avec le système d'exploitation" style="margin: 0 auto; display: block;" /></a></p>
<p>Quelles sont les options ?</p>
<p>On <em>pourrait</em> essayer de créer des correspondances inscrites en dur dans le moteur (à la façon de ce qui est fait entre WebAssembly et JavaScript d’une part et entre JavaScript et WebIDL d’autre part).</p>
<p>Mais pour ce faire, il faudrait une correspondance spécifique par langage. Le moteur aurait à prendre en charge chacune de ces correspondances explicitement et les mettre à jour à chaque changement de chaque langage. Bref, c’est la pagaille.</p>
<p>C’est de cette façon que furent conçus les premiers compilateurs. Il existait une trajectoire différente entre chaque langage source et chaque langage machine. Nous en parlions plus en détails dans <a href="https://tech.mozfr.org/post/2017/03/08/Une-introduction-cartoonesque-a-WebAssembly">un des premiers billets sur WebAssembly</a>.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-05-langs05.png" title="Un diagramme avec différents langages sources sur la gauche vers différentes plateformes matérielles (x86 / ARM) représentées par une créature protéiforme"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-05-langs05_m.png" alt="Un diagramme avec différents langages sources sur la gauche vers différentes plateformes matérielles (x86 / ARM) représentées par une créature protéiforme" style="margin: 0 auto; display: block;" /></a></p>
<p>On ne veut pas avoir quelque chose d’aussi compliqué. On veut que chaque langage puisse parler à chaque plateforme. Et en même temps, on veut que cette approche soit extensible.</p>
<p>Il nous faut donc une autre approche et on peut s’inspirer des architectures des compilateurs modernes. Pour ceux-ci, il y a une division entre le front-end et le back-end. La partie front-end porte sur le langage source traduit en une représentation intermédiaire abstraite. La partie back-end part de cette représentation intermédiaire jusqu’au code machine cible.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-06-langs06.png" title="Contrairement au diagramme précédent, les flèches convergent vers une zone intermédiaire avec 'IR' de là repartent de nouvelles flèches vers les plateformes."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-06-langs06_m.png" alt="Contrairement au diagramme précédent, les flèches convergent vers une zone intermédiaire avec 'IR' de là repartent de nouvelles flèches vers les plateformes." style="margin: 0 auto; display: block;" /></a></p>
<p>C’est de cette méthode dont s’inspirent les types Web IDL. Quand on le regarde d’un autre angle, Web IDL ressemble un peu à une représentation intermédiaire.</p>
<p>Ceci étant posé, Web IDL est assez spécifique au Web. Et il existe de nombreux cas d’usage pour WebAssembly en dehors du Web. Web IDL n’est donc pas la représentation intermédiaire qu’il faut.</p>
<p>Malgré cela, pouvons-nous nous inspirer de Web IDL et créer un nouvel ensemble de types abstraits ?</p>
<p>C’est ainsi qu’on arrive à la proposition pour les types d’interfaçage WebAssembly.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/04-06-types-as-IR.png" title="Un diagramme avec un module WASM à gauche, une liste de langages qui compilent vers WASM et des doubles flèches qui pointent vers le texte 'Types d'interfaçage WebAssembly'. De là repartent des doubles flèches vers des langages, des environnements, des systèmes d'exploitation, etc."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.04-06-types-as-IR_m.png" alt="Un diagramme avec un module WASM à gauche, une liste de langages qui compilent vers WASM et des doubles flèches qui pointent vers le texte 'Types d'interfaçage WebAssembly'. De là repartent des doubles flèches vers des langages, des environnements, des systèmes d'exploitation, etc." style="margin: 0 auto; display: block;" /></a></p>
<p>Ces types ne sont pas des types concrets. Ils ne ressemblent pas aux types qu’on trouve aujourd’hui dans WebAssembly comme <code>int32</code> ou <code>float64</code>. On ne peut pas les manipuler avec des opérations en WebAssembly.</p>
<p>On n’ajoutera par exemple pas de méthode de concaténation de chaînes de caractères dans WebAssembly. Toutes les opérations seront effectuées sur les types concrets à chaque extrêmité.</p>
<p>La clef de voûte de ce fonctionnement est la copie des valeurs d’un côté à l’autre. Plutôt que de partager une représentation commune, les deux parties utilisent les types d’interfaçage pour copier les valeurs.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/04-07-copy.png" title="Le module WASM dit 'vu que c'est une chaîne de caractères en mémoire linéaire, je sais comment la manipuler' et le navigateur dit 'vu que c'est une DOMString, je sais comment la manipuler'. Sous le capôt, le moteur copie le tampon de mémoire linéaire WASM dans le tas du renderer."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.04-07-copy_m.png" alt="Le module WASM dit 'vu que c'est une chaîne de caractères en mémoire linéaire, je sais comment la manipuler' et le navigateur dit 'vu que c'est une DOMString, je sais comment la manipuler'. Sous le capôt, le moteur copie le tampon de mémoire linéaire WASM dans le tas du renderer." style="margin: 0 auto; display: block;" /></a></p>
<p>Il existe un point qui pourrait constituer une exception à cette règle : les nouvelles valeurs de référence (telles que <code>anyref</code>) que nous avons mentionnées plus haut. Dans ce cas, c’est le pointeur vers l’objet qui est copié entre les deux côtés. Les deux pointeurs pointent donc vers la même chose. En théorie, cela peut vouloir dire qu’ils ont besoin de partager une représentation.</p>
<p>Dans les cas où la référence ne fait que “traverser” un module WebAssembly (comme l’exemple que nous avons vu avec <code>anyref</code>), les deux interlocuteurs n’ont pas à partager une représentation. Le module n’est pas supposé comprendre ce type mais simplement le passer entre les fonctions.</p>
<p>Il existe cependant des scénarios où on souhaite que les interlocuteurs partagent une représentation. Par exemple, la proposition pour le ramasse-miettes ajoute une méthode pour <a href="https://github.com/WebAssembly/gc/blob/master/proposals/gc/MVP-JS.md#type-definition-objects">créer des défintions de type</a> afin que les deux parties puissent partager des représentations. Dans ces cas, le choix de la représentation et de ce qu’il faut partager est effectué par les développeurs qui conçoivent l’API.</p>
<p>Cette approche rend le dialogue beaucoup plus simple entre un module WebAssembly et de nombreux langages.</p>
<p>Dans certains cas (comme celui du navigateur), la correspondance entre les types d’interfaçage et les types du système sous-jacent sera inscrite en dur.</p>
<p>Ainsi, une partie des correspondances est construite à la compilation tandis que l’autre est fourni au moteur lors du chargement du contenu.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/04-08-mapping-symmetry-host.png" title="Le moteur lit le livret et dit 'OK, donc ça ça correspond à une chaîne ? Je peux donc utiliser mes correspondances en dur afin de la convertir en DOMString pour la fonction qui le demande'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.04-08-mapping-symmetry-host_m.png" alt="Le moteur lit le livret et dit 'OK, donc ça ça correspond à une chaîne ? Je peux donc utiliser mes correspondances en dur afin de la convertir en DOMString pour la fonction qui le demande'" style="margin: 0 auto; display: block;" /></a></p>
<p>Dans les autres cas, par exemple quand deux modules WebAssembly échangent entre eux, les deux envoient leurs livrets d’instruction qui décrivent chacun leurs correspondances entre les types de fonction et les types abstraits.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/04-09-mapping-symmetry-wasm.png" title="Un module Rust compilé en WASM et un module Go compilé en WASM fournissent chacun un livret au moteur qui dit : 'OK, voyons voir comment assembler tout ça'."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.04-09-mapping-symmetry-wasm_m.png" alt="Un module Rust compilé en WASM et un module Go compilé en WASM fournissent chacun un livret au moteur qui dit : 'OK, voyons voir comment assembler tout ça'." style="margin: 0 auto; display: block;" /></a></p>
<p>Ce n’est pas la seule chose nécessaire pour que des modules écrits avec différents langages sources se parlent (nous reviendrons sur ce sujet) mais c’est un grand pas dans cette direction.</p>
<h2>À quoi ressemblent ces types d’interfaçage ?</h2>
<p>Avant d’aller plus loin dans les détails, rappelons que cette proposition est toujours en cours de développement. Le résultat final pourrait s’avérer complètement différent.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/05-01-construction.png" title="Deux bonhommes sont en train de placer des plots de chantier et l'un tient un panneau avec 'À utiliser avec précaution'."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.05-01-construction_m.png" alt="Deux bonhommes sont en train de placer des plots de chantier et l'un tient un panneau avec 'À utiliser avec précaution'." style="margin: 0 auto; display: block;" /></a></p>
<p>De plus, tout est géré par le compilateur. Même après que cette proposition ait été finalisée, vous aurez uniquement à connaître les annotations attendues par la chaîne de compilation pour les mettre dans votre code (à la façon de ce que nous avons fait avec <code>wasm-bindgen</code> plus haut). Il n’est pas vraiment nécessaire de savoir comment ça fonctionne sous le capot.</p>
<p>Vu que <a href="https://github.com/WebAssembly/interface-types/blob/master/proposals/interface-types/Explainer.md">les détails exposés par la proposition</a> sont assez clairs, profitons-en pour voir comment tout cela s’articule.</p>
<h3>Le problème à résoudre</h3>
<p>Le problème consiste à traduire des valeurs entre différents types lorsqu’un module dialogue avec un autre module (ou avec un hôte comme le navigateur).</p>
<p>On a quatre endroits où on peut avoir besoin de traduire :</p>
<ul>
<li>Pour les fonctions exportées
<ul>
<li>la réception de paramètres depuis l’appelant</li>
<li>l’envoi des valeurs de retour vers l’appelant</li>
</ul></li>
<li>Pour les fonctions importées
<ul>
<li>le passage des paramètres à la fonction</li>
<li>la réception des valeurs de retour</li>
</ul></li>
</ul>
<p>On peut voir chacun de ces cas comme un mouvement sur deux directions :</p>
<ul>
<li>La montée pour les valeurs qui quittent le module. Elles passent d’un type concret à un type d’interfaçage.</li>
<li>La descente pour les valeurs qui arrivent dans le module. Elles passent d’un type d’interfaçage à un type concret.</li>
</ul>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/05-02-incoming-outgoing.png" title="Un schéma avec deux modules WASM qui s'échangent des infos. Les valeurs envoyées remontent et les valeurs reçues redescendent le long des flèches"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.05-02-incoming-outgoing_m.png" alt="Un schéma avec deux modules WASM qui s'échangent des infos. Les valeurs envoyées remontent et les valeurs reçues redescendent le long des flèches" style="margin: 0 auto; display: block;" /></a></p>
<h3>Indiquer au moteur les transformations à effectuer entre les types concrets et les types d’interfaçage</h3>
<p>Il faut donc une méthode pour indiquer au moteur les transformations à appliquer aux paramètres et aux valeurs de retour d’une fonction. Comment faire ?</p>
<p>En définissant un adaptateur d’interface.</p>
<p>Prenons l’exemple d’un module Rust compilé en WebAssembly. Ce module exporte une fonction <code>greeting_</code> qui peut être appelée sans paramètre et qui renvoie un message de salutation.</p>
<p>Voici ce qu’on aurait actuellement (avec le format textuel WebAssembly).</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/05-03-original-function.png" title="L'équivalent texte WebAssembly avec une annotation sur le retour : la fonction renvoie deux entiers."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.05-03-original-function_m.png" alt="L'équivalent texte WebAssembly avec une annotation sur le retour : la fonction renvoie deux entiers." style="margin: 0 auto; display: block;" /></a></p>
<p>Pour le moment, la fonction renvoie deux entiers.</p>
<p>Mais on voudrait qu’elle renvoie une valeur pour le type d’interfaçage <code>string</code>. On ajoute donc quelque chose qu’on appelle un adaptateur d’interface.</p>
<p>Si un moteur prend en charge les types d’interfaçage, lorsqu’il verra un adaptateur d’interface, il enveloppera le module dans cette interface.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/05-04-interface.png" title="Le code précédent est grisé et une partie avec l'interface est ajoutée. L'annotation indique que celle-ci renvoie une chaîne de caractères."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.05-04-interface_m.png" alt="Le code précédent est grisé et une partie avec l'interface est ajoutée. L'annotation indique que celle-ci renvoie une chaîne de caractères." style="margin: 0 auto; display: block;" /></a></p>
<p>Le module n’exporte plus la fonction <code>greeting_</code> mais la fonction <code>greeting</code> qui enveloppe l’originale. La nouvelle fonction <code>greeting</code> renvoie une chaîne de caractères et plus deux entiers.</p>
<p>On obtient une compabilité ascendante, car les moteurs qui ne comprennent pas les types d’interface exporteront la fonction originale <code>greeting_</code> (celle qui renvoie deux entiers).</p>
<p>Comment l’adaptateur d’interface explique au moteur comment transformer deux entiers en une chaîne ?</p>
<p>Il utilise une séquence d’instructions d’adaptateur.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/05-05-adapter-inst-return.png" title="Le code précédent est grisé et deux nouvelles lignes sont ajoutée. La première indique l'appel de la fonction adaptée et la deuxième indique comment adapter la valeur de retour afin de produire une chaîne de caractères"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.05-05-adapter-inst-return_m.png" alt="Le code précédent est grisé et deux nouvelles lignes sont ajoutée. La première indique l'appel de la fonction adaptée et la deuxième indique comment adapter la valeur de retour afin de produire une chaîne de caractères" style="margin: 0 auto; display: block;" /></a></p>
<p>Les instructions d’adaptateur présentées dans cette image sont deux exemples d’un ensemble d’instructions qui sont définies dans cette proposition.</p>
<p>Voici ce que font les instructions précédentes :</p>
<ol>
<li>Utiliser l’instruction d’adaptateur <code>call-export</code> afin d’appeler la méthode originale <code>greeting_</code>. C’est la fonction exportée par le module original qui renvoie deux nombres. Ces deux nombres sont placés sur la pile.</li>
<li>Utiliser l’instruction d’adaptateur <code>memory-to-string</code> qui convertit les nombres en une séquence d’octets qui composent la chaînes de caractères. On doit ici préciser <code>"mem"</code> à la suite car un module WebAssembly pourrait demain avoir plusieurs espaces mémoire. On indique ainsi au moteur l’espace mémoire à consulter. Le moteur prend alors les deux nombres sur le dessus de la pile (qui correspondent au pointeur et à la longueur) et les utilise afin de déterminer les octets à utiliser.</li>
</ol>
<p>Cela ressemble un peu à un langage de programmation, mais il n’y a pas de contrôle du flux d’instructions ici (pas de boucles ou d’instructions conditionnelles). Il s’agit d’un langage déclaratif qui nous permet de fournir des instructions au moteur.</p>
<p>À quoi cela ressemblerait-il si notre fonction prenait une chaîne en paramètre (le nom de la personne à saluer par exemple).</p>
<p>Eh bien c’est assez proche. On modifie l’interface de la fonction d’adaptation afin d’ajouter le paramètre et on ajoute ensuite deux instructions d’adaptateur.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/05-06-adapter-inst-param.png" title="Les lignes précédentes sont grisées. On voit l'ajout d'un premier fragment pour le paramètre qui est une chaîne de caractères. Ensuite une ligne ajoute une référence sur la pile pour référencer la chaîne passée en argument. Enfin, la troisième ligne indique comment interpréter les octets de la chaînes pour les placer en mémoire linéaire."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.05-06-adapter-inst-param_m.png" alt="Les lignes précédentes sont grisées. On voit l'ajout d'un premier fragment pour le paramètre qui est une chaîne de caractères. Ensuite une ligne ajoute une référence sur la pile pour référencer la chaîne passée en argument. Enfin, la troisième ligne indique comment interpréter les octets de la chaînes pour les placer en mémoire linéaire." style="margin: 0 auto; display: block;" /></a></p>
<p>Voilà ce que font ces nouvelles instructions :</p>
<ol>
<li>Utiliser l’instruction <code>arg.get</code> afin d’obtenir une référence à l’objet qu’est la chaîne de caractères qu’on place sur la pile.</li>
<li>Utiliser l’instruction <code>string-to-memory</code> afin de récupérer les octets de cet objet pour les placer en mémoire linéaire. Là encore, on précise l’espace mémoire dans lequel inscrire ces octets. On précise également comment allouer ces octets. Pour cela on fournit une fonction d’allocation (qui pourrait être un export fourni par le module).</li>
</ol>
<p>Si vous souhaitez en savoir plus sur ce fonctionnement, vous pouvez consulter <a href="https://github.com/WebAssembly/interface-types/blob/master/proposals/interface-types/Explainer.md">cette explication qui va plus en détails</a>.</p>
<h3>Envoyer les instructions au moteur</h3>
<p>Comment envoyer tout cela au moteur ?</p>
<p>Ces annotations sont ajoutées au fichier binaire dans une section spécifique (<em>custom</em>).</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/05-07-custom-section.png" title="Un fichier divisé en deux parties : la première (la plus grande) contient les sections « connues » avec le code et les données : la seconde, « spécifique », contient les adaptateurs d'interface."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.05-07-custom-section_m.png" alt="Un fichier divisé en deux parties : la première (la plus grande) contient les sections « connues » avec le code et les données : la seconde, « spécifique », contient les adaptateurs d'interface." style="margin: 0 auto; display: block;" /></a></p>
<p>Si un moteur sait exploiter les types d’interfaçage, il pourra utiliser cette section. Sinon, il pourra l’ignorer et vous pourrez utiliser une prothèse (<em>polyfill</em>) afin de lire la section et écrire du code de liaison.</p>
<h2>Quelles différences avec CORBA, <em>Protocol buffers</em>, etc. ?</h2>
<p>Il existe actuellement d’autres standards qui semblent résoudre ce même problème dont CORBA, Protocol buffers, Cap’n Proto.</p>
<p>En quoi ceux-ci sont différents ? Ils résolvent un problème beaucoup plus difficile.</p>
<p>Ils ont été conçus afin de pouvoir interagir avec un système avec lequel on ne partage pas de mémoire (soit parce qu’il s’agit d’un autre processus ou d’une toute autre machine sur le réseau).</p>
<p>Cela signifie qu’il faut pouvoir envoyer cette représentation intermédiaire par-delà cette frontière.</p>
<p>Ces standards visent à définir un format de sérialisation qui puisse efficacement voyager sur cette frontière. C’est là un des aspects essentiels de ces standards.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/06-01-cross-boundary-ir.png" title="Deux machines qui se parlent avec chacune un module WASM. Entre ces deux machines une flèche annotée « IR » pour la représentation intermédiaire qui voyage sur cet axe."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.06-01-cross-boundary-ir_m.png" alt="Deux machines qui se parlent avec chacune un module WASM. Entre ces deux machines une flèche annotée « IR » pour la représentation intermédiaire qui voyage sur cet axe." style="margin: 0 auto; display: block;" /></a></p>
<p>Bien que le problème semble similaire, il s’agit en fait de l’exact inverse.</p>
<p>Avec les types d’interfaçage, la représentation intermédiaire (l’« IR ») ne quitte jamais le moteur. Elle n’est même pas visible pour les modules.</p>
<p>Les modules ne voient que ce le moteur leur fournit à la fin (ce qui a été copié sur leur mémoire linéaire ou fourni comme référence). Il n’est pas nécessaire d’indiquer au moteur l’organisation de ces types, car elle n’est pas définie.</p>
<p>Ce qui est défini, en revanche, est la façon de parler au moteur. Il s’agit du langage déclaratif utilisé pour écrire ce livret envoyé au moteur.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/06-02-no-boundary-ir.png" title="Les deux modules WASM ne sont plus reliés par une flèche, l'IR ne voyage plus le long d'un axe."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.06-02-no-boundary-ir_m.png" alt="Les deux modules WASM ne sont plus reliés par une flèche, l'IR ne voyage plus le long d'un axe." style="margin: 0 auto; display: block;" /></a></p>
<p>De cet aspect déclaratif découle un effet de bord appréciable : le moteur peut détecter lorsqu’une « traduction » entre types est superflue. Ainsi si les deux modules qui discutent utilisent le même type, le moteur évitera cette double transformation.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/06-03-opt.png" title="Le moteur, entouré d'un module Rust compilé en WASM et d'un module Go compilé en WASM, dit 'Ohoh, vous utilisez tous les deux une mémoire linéaire pour cette chaîne de caractères. Dans ce cas, je vais juste faire une rapide copie entre vos mémoires"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.06-03-opt_m.png" alt="Le moteur, entouré d'un module Rust compilé en WASM et d'un module Go compilé en WASM, dit 'Ohoh, vous utilisez tous les deux une mémoire linéaire pour cette chaîne de caractères. Dans ce cas, je vais juste faire une rapide copie entre vos mémoires" style="margin: 0 auto; display: block;" /></a></p>
<h2>Comment utiliser tout ça aujourd’hui ?</h2>
<p>Comme nous l’avons indiqué plus haut, il s’agit d’une proposition au stade encore expérimental. Certaines choses risquent de changer rapidement et il serait risqué d’utiliser tout ça en production.</p>
<p>Ceci étant posé, si vous souhaitez manipuler tout ça, nous avons implémenté le nécessaire sur l’ensemble de la chaîne de compilation : de la production de code à la consommation :</p>
<ul>
<li>La chaîne de compilation Rust</li>
<li><code>wasm-bindgen</code> </li>
<li>L’environnement d’exécution WebAssembly Wasmtime</li>
</ul>
<p>Comme nous maintenons ces outils et que nous travaillons sur le standard, nous pouvons maintenir le nécessaire pendant le développement du standard.</p>
<p>Bien que tout ça continue d’évoluer, nous nous assurons de synchroniser ces évolutions avec ces outils. Ainsi, tant que vous utilisez des versions à jour de ces outils, vous ne devriez pas rencontrer trop de problèmes.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/07-01-construction.png" title="Un bonhomme avec un casque et à proximité de plots de chantier qui dit « faites simplement attention à bien rester sur le chemin balisé »."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.07-01-construction_m.png" alt="Un bonhomme avec un casque et à proximité de plots de chantier qui dit « faites simplement attention à bien rester sur le chemin balisé »." style="margin: 0 auto; display: block;" /></a></p>
<p>Voici donc les nombreuses façons dont vous pouvez utiliser tout ça aujourd’hui. Pour une version à jour, vous pouvez consulter <a href="https://github.com/CraneStation/wasmtime-demos">ce dépôt de démonstrations</a>.</p>
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/Qn_4F3foB3Q" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<h2>Remerciements</h2>
<ul>
<li>Merci à l’équipe qui a assemblé toutes ces pièces pour tous ces langages et tous ces environnements d’exécution : Alex Crichton, Yury Delendik, Nick Fitzgerald, Dan Gohman et Till Schneidereit</li>
<li>Merci aux porteurs de cette proposition et à leurs collègues pour leur travail dessus : Luke Wagner, Francis McCabe, Jacob Gravelle, Alex Crichton et Nick Fitzgerald</li>
<li>Merci à mes merveilleux collègues : Luke Wagner et Till Schneidereit pour leurs retours et contributions inestimables à cet article.</li>
</ul>
<h2>À propos de Lin Clark</h2>
<p>Lin travaille au sein de l’équipe ‘Advanced Development’ de Mozilla et notamment sur Rust et WebAssembly.</p>
<ul>
<li><a href="https://twitter.com/linclark">@linclark</a></li>
</ul>
<p><style>
pre { white-space: pre;}
</style></p>
Une introduction cartoonesque à WebAssemblyurn:md5:2cee041d928c086a548a3be16be135c72017-03-08T19:10:00+01:002017-03-09T20:26:00+01:00sphinxJavaScriptJavaScriptWASMWebAssembly<p><em>Cet article est le premier d’une série de traductions d’articles écrits par <a href="http://code-cartoons.com/">Lin Clark</a> et publiés sur le blog Hacks. <a href="https://hacks.mozilla.org/2017/02/a-cartoon-intro-to-webassembly/">La version anglaise est disponible ici</a>. Merci à Adam, dattaz, Jeremie et à goofy et Benjamin pour la relecture :)</em></p>
<hr />
<p>WebAssembly est rapide. Vous avez sans doute déjà entendu ça. Mais qu’est-ce qui rend WebAssembly si rapide ?
Dans cette série d’articles, c’est exactement ce que je compte vous expliquer.</p>
<h2>Mhmmm, c’est quoi au juste WebAssembly ?</h2>
<p>WebAssembly est une façon de prendre du code écrit dans un langage de programmation différent de JavaScript et de le faire s’exécuter dans le navigateur. Ainsi, quand les gens disent que WebAssembly est rapide, c’est en le comparant à JavaScript.</p>
<p>Maintenant, que l’on soit clair, je ne veux pas dire qu’il s’agit d’une situation binaire où il faudrait utiliser uniquement WebAssembly ou uniquement JavaScript. En fait c’est l’inverse, on s’attend à ce que les développeurs utilisent les deux dans la même application.</p>
<p>Cependant, il est utile de comparer les deux afin de mieux comprendre le potentiel de WebAssembly.</p>
<h2>Un peu d’histoire des performances</h2>
<p>JavaScript est né en 1995. Il n’a pas été conçu pour être rapide et en effet il ne l’était pas pendant les dix premières années.
À partir de ce moment, la compétition entre navigateurs s’amplifia.</p>
<p>En 2008, commença alors une période qu’on a appelée « la guerre de la performance ». Plusieurs navigateurs se sont dotés de compilateurs à la volée (ou « JIT » pour « Just In Time »). Lors de l’exécution de JavaScript, le compilateur JIT peut identifier différents motifs et rendre le code bien plus rapide grâce à ces derniers.</p>
<p>L’introduction de ces compilateurs JIT a conduit à un point d’inflexion pour les performances de JavaScript. Son exécution est devenue 10 fois plus rapide.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/01-01-perf_graph05.png" alt="Courbe décrivant la progression des performances pour JavaScript" /></p>
<p>Avec cette performance accrue, JavaScript commença à être utilisé dans des domaines jusqu’alors insoupçonnés, par exemple pour de la programmation côté serveur avec Node.js.
L’amélioration des performances a rendu possible l’utilisation de JavaScript pour traiter un tout nouvel ensemble de problèmes.</p>
<p>Avec WebAssembly, nous pourrions bien être à un nouveau point d’inflexion.
<img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/01-02-perf_graph10.png" alt="Courbe décrivant la progression des performances pour JavaScript jusqu'à aujourd'hui" /></p>
<p>Rentrons dans les détails afin de comprendre ce qui rend WebAssembly si rapide.</p>
<h3>Contexte</h3>
<ul>
<li><a href="https://tech.mozfr.org/post/2017/03/08/Un-petit-cours-accelere-de-compilation-a-la-volee-(JIT)">Cours accéléré de JIT (compilation à la volée)</a></li>
<li><a href="https://tech.mozfr.org/post/2017/03/08/Un-petit-cours-accelere-d-assembleur">Cours accéléré d’assembleur</a></li>
</ul>
<h3>WebAssembly, aujourd’hui</h3>
<ul>
<li><a href="https://tech.mozfr.org/post/2017/03/08/Creer-et-manipuler-des-modules-WebAssembly">Créer et travailler avec les modules WebAssembly</a></li>
<li><a href="https://tech.mozfr.org/post/2017/03/08/D-ou-vient-la-rapidite-de-WebAssembly">Qu’est-ce qui rend WebAssembly si rapide ?</a></li>
</ul>
<h3>L’avenir de WebAssembly</h3>
<ul>
<li><a href="https://tech.mozfr.org/post/2017/03/08/WebAssembly-aujourd-hui-et-demain">Où en est WebAssembly à l’heure actuelle et quelles sont les prochaines étapes ?</a></li>
</ul>
<h2>À propos de <a href="http://twitter.com/linclark">Lin Clark</a></h2>
<p>Lin est ingénieure au sein de l’équipe Mozilla Developer Relations. Elle bidouille avec JavaScript, WebAssembly, Rust et Servo et crée des bandes dessinées sur le code.</p>
Un petit cours accéléré de compilation à la volée (JIT)urn:md5:00b41d08284befeebc4f39d117e25f1d2017-03-08T18:54:00+01:002017-03-09T20:25:32+01:00sphinxJavaScriptJavaScriptWASMWebAssembly<p><em>Cet article est le deuxième d’une série de traductions d’articles écrits par <a href="http://code-cartoons.com/">Lin Clark</a> et publiés sur le blog Hacks. <a href="https://hacks.mozilla.org/2017/02/a-crash-course-in-just-in-time-jit-compilers/">La version anglaise est disponible ici</a>. Merci à dattaz, Jeremie et à goofy et Benjamin pour la relecture :) Si vous n’avez pas lu les autres articles, nous vous conseillons de démarrer <a href="https://tech.mozfr.org/post/2017/03/08/Une-introduction-cartoonesque-a-WebAssembly">depuis le début</a>.</em></p>
<hr />
<p>Si les débuts de JavaScript sont marqués par une lenteur, il est devenu sensiblement plus rapide grâce à un truc appelé JIT. OK, mais comment fonctionne ce fameux JIT ?</p>
<h2>Comment JavaScript est exécuté par les navigateurs</h2>
<p>Quand le développeur que vous êtes ajoute du JavaScript dans une page web, vous avez un objectif et un problème.
Objectif : vous voulez dire à l’ordinateur ce qu’il doit faire.
Problème : l’ordinateur et vous ne parlez pas du tout le même langage.</p>
<p>Vous, vous parlez un langage humain ; l’ordinateur, lui, parle un langage de machine. Même si vous pensez que JavaScript ou n’importe quel autre langage de programmation de haut niveau n’est pas un langage humain, ne vous y trompez pas, c’est bien le cas. Ils ont été créés pour se conformer au mode de pensée des humains, pas des machines.</p>
<p>Ainsi, le travail du moteur JavaScript consiste à prendre votre langage humain et le convertir en quelque chose qu’une machine peut comprendre.
Je vois ça comme dans le film <a href="https://fr.wikipedia.org/wiki/Premier_Contact_(film)">Premier Contact</a>, dans lequel des humains et des extraterrestres essaient de se parler.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/02-01-alien03.png" alt="Communiquer avec un alien ?" /></p>
<p>Dans ce film, les humains et les extraterrestres ne font pas de traduction mot à mot. Les deux groupes ont différentes façons de penser le monde. Eh bien figurez-vous que c’est la même chose entre les humains et les machines (on verra ça en détail dans le prochain article).</p>
<p>Donc, comment se fait cette traduction?</p>
<p>Dans le monde de la programmation il existe généralement deux façons de faire une traduction vers du langage machine : en utilisant un interpréteur ou en utilisant un compilateur.</p>
<p>Avec un interpréteur, cette traduction se fait en temps réel, quasiment ligne par ligne.
<img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/02-02-interp02.png" alt="Interpréter un langage" /></p>
<p>D’un autre côté, un compilateur ne fait pas une traduction en temps réel, il travaille en amont pour créer sa traduction et la retranscrire intégralement.
Avec un interpréteur, cette traduction se fait en temps réel, quasiment ligne par ligne.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/02-03-compile02.png" alt="Compiler un langage" /></p>
<p>Chacune de ces deux façons de procéder présente des avantages et des inconvénients.</p>
<h3>Le pour et le contre des interpréteurs</h3>
<p>Les interpréteurs sont rapides à l’allumage. Ils n’ont pas franchir toutes les étapes de compilation avant de pouvoir exécuter quoi que ce soit. Ils commencent la traduction de la première ligne et l’exécutent immédiatement.</p>
<p>Grâce à ça, un interpréteur semble naturellement être un bon choix pour exécuter quelque chose comme JavaScript. C’est important pour un développeur web de pouvoir commencer à exécuter son code aussi vite que possible. C’est pour cette raison les navigateurs ont utilisé des interpréteurs pour exécuter JavaScript à leur début.</p>
<p>Le problème avec les interpréteurs survient quand vous voulez exécuter le même code plus d’une fois. Typiquement quand vous utilisez une boucle. Dans ce cas, l’interpréteur doit faire la même traduction encore et encore.</p>
<h3>Le pour et le contre des compilateurs</h3>
<p>Un compilateur choisit les compromis opposés.</p>
<p>Il a besoin d’un peu plus de temps au démarrage parce qu’il doit passer par toutes les étapes de compilation avant de pouvoir faire quoi que ce soit. Cependant exécuter le code d’une boucle est bien plus rapide puisqu’il n’est plus nécessaire de refaire le travail de traduction à chaque passage dans la boucle.</p>
<p>Une autre différence tient à ce que les compilateurs ont plus de temps pour observer le code et le modifier pour qu’il puisse s’exécuter plus rapidement. Ces modifications ne sont ni plus ni moins que des optimisations. Comme les interpréteurs font le travail de traduction en même temps qu’ils exécutent le code, ils ne peuvent pas se permettre de prendre beaucoup de temps pour faire des optimisations.</p>
<h2>Les compilateurs « juste à temps » : le meilleur des deux mondes</h2>
<p>Afin de passer outre l’inefficacité des interpréteurs — devoir traduire le même code encore et encore — les navigateurs ont commencé à leur adjoindre des compilateurs.</p>
<p>Chaque navigateur le fait de manière légèrement différente, cependant l’idée de base reste la même. On ajoute une nouvelle pièce au moteur JavaScript : un profileur de code. Ce profileur observe le code pendant qu’il s’exécute et prend des notes sur le nombre de fois qu’est exécuté un bout de code et sur les types utilisés.</p>
<p>Au début le profileur fait tout passer dans l’interpréteur.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/02-04-jit02.png" alt="Passage dans l'interpréteur" /></p>
<p>Si les mêmes lignes de code sont exécutées quelques fois, ce bout de code est considéré comme « tiède ». S’il est exécuté très souvent il est considéré comme « chaud ».</p>
<h3>Compilateur de base</h3>
<p>Quand une fonction devient tiède, le JIT va l’envoyer au compilateur et va stocker le résultat de la compilation.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/02-05-jit06.png" alt="Le compilateur de base (BC pour Baseline Compiler)" /></p>
<p>Chaque ligne de code est compilée sous forme d’un « extrait » (NDT <em>stub</em> en anglais). Les extraits sont indexés par numéro de ligne et par type de variable (j’expliquerai pourquoi c’est important plus tard). Si le profileur remarque que le même code avec les mêmes types de variables est exécuté à nouveau il utilisera simplement l’extrait compilé.</p>
<p>Ça aide à accélérer les choses. Mais comme je le disais, un compilateur peut faire bien plus. Il peut prendre le temps de comprendre la façon la plus efficace de faire certaines choses… de faire des optimisations. Le compilateur de base va faire quelques-unes de ces optimisations (j’en donne un exemple ci-après). Cela ne doit pas prendre trop de temps, car il ne veut pas bloquer l’exécution trop longtemps.</p>
<p>Cependant, si ce code est vraiment chaud — s’il est exécuté vraiment très souvent — alors ça vaut la peine de prendre le temps de faire davantage d’optimisations.</p>
<h3>Compilateur optimisant</h3>
<p>Quand un bout de code est vraiment chaud, le profileur va demander une compilation optimisée. Cela va créer une autre version encore plus rapide de ce code qui sera lui aussi stocké.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/02-06-jit09.png" alt="Le compilateur optimisant (OC pour Optimizing Compiler)" /></p>
<p>Pour pouvoir réaliser une version plus rapide du code, le compilateur va devoir émettre quelques hypothèses.
Par exemple, s’il peut supposer que tous les objets créés par un constructeur donné auront toujours la même structure — en clair, s’ils ont toujours les même propriétés et que ces propriétés sont toujours instanciées dans le même ordre — alors il va prendre des raccourcis pour ce cas spécifique.</p>
<p>Le compilateur utilise les informations que le profileur a glanées à force d’observations pour formuler de telles hypothèses. Si quelque chose s’est révélé vrai pour toutes les boucles précédentes, alors il partira du principe que ça continuera à être vrai.</p>
<p>Bien évidemment, avec JavaScript il n’y a jamais de telles garanties. Vous pouvez avoir 99 objets qui ont tous la même structure mais le centième peut avoir une propriété manquante.</p>
<p>Ainsi, le compilateur a besoin de vérifier la validité des hypothèses avant de pouvoir exécuter le code. Si elles sont valides, alors on exécute le code compilé. Mais dans le cas contraire, le JIT va partir du principe que les hypothèses sont fausses et va mettre le code optimisé à la poubelle.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/02-07-jit11.png" alt="Rejet du code mal optimisé" /></p>
<p>À ce moment-là, l’exécution du code va à nouveau se faire soit au niveau de l’interpréteur soit via le code de base compilé précédemment. On appelle ce processus la dé-optimisation (ou encore le rappel).</p>
<p>Habituellement, la compilation optimisée produit du code plus rapide, cependant, dans certains cas cela peut conduire à des problèmes de performance inattendus. Si vous avez du code qui n’arrête pas d’être optimisé puis dé-optimisé, vous pouvez vous retrouver avec du code plus lent à s’exécuter que la version compilée de base.</p>
<p>La plupart des navigateurs ont mis en place des limites pour sortir de ces cycles optimisation/dé-optimisation lorsqu’ils se présentent. Si le JIT a réalisé, disons, dix tentatives d’optimisation pour finalement devoir s’en débarrasser à chaque fois, alors il arrêtera de vouloir faire de l’optimisation.</p>
<h3>Un exemple d’optimisation : la spécialisation de type</h3>
<p>Il y a tout un tas d’optimisations possibles, je vais cependant vous en montrer une pour vous donner une idée de la manière dont les choses se passent. Un des gains les plus notables lors d’une compilation optimisée vient de ce que l’on appelle la spécialisation de type.</p>
<p>Le système de type dynamique utilisé par JavaScript requiert un peu plus de travail qu’il n’y paraît lors de l’exécution. Par exemple, prenons le code suivant:</p>
<pre><code>function arraySum(arr) {
var sum = 0;
for (var i = 0; i < arr.length; i++) {
sum += arr[i];
}
}
</code></pre>
<p>L’étape <code>+=</code> dans la boucle semble assez simple au premier abord. On pourrait penser que cela se calcule en une étape, malheureusement, à cause de la nature dynamique des types, ça va prendre plus d’étapes qu’on ne le croirait. Partons du principe que <code>arr</code> est un tableau (<code>Array</code>) de 100 entiers. Dès que le code va se réchauffer, le compilateur de base va créer un bout de code compilé pour chacune des opérations de la fonction. On va donc obtenir un bout de code pour <code>sum += arr[i]</code> qui va s’occuper de gérer l’opération <code>+=</code> comme une addition d’entiers.</p>
<p>Cependant, il n’y a aucune garantie que <code>sum</code> et <code>arr[i]</code> soient des entiers. Puisque les types sont dynamiques en JavaScript, il est toujours possible que, lors d’une des itérations de la boucle, <code>arr[i]</code> soit une chaîne de caractères. Additionner des entiers et concaténer des chaînes sont deux opérations très différentes qui donneront lieu à des codes compilés très différents.</p>
<p>Le JIT résout ce problème en compilant un grand nombre de bouts de code différents. Si du code est monomorphique (c’est-à-dire qu’il est appelé toujours avec les mêmes types) on aura un extrait de code compilé spécifique. Si du code est polymorphique (c’est-à-dire qu’il est appelé avec différents types d’une exécution à l’autre), alors on aura un bout de code compilé pour chaque combinaison de type utilisée dans cette opération.</p>
<p>Ça signifie que le JIT va devoir poser pas mal de questions afin de pouvoir choisir le bon bout de code compilé à exécuter.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/02-08-decision_tree01.png" alt="Arborescence des choix possibles" /></p>
<p>Puisque chaque ligne de code a son propre ensemble de bouts de code compilé, le JIT va devoir vérifier les types en jeu à chaque fois que la ligne de code est exécutée. Ainsi pour chaque itération de boucle, il devra sans cesse reposer les même questions.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/02-09-jit_loop02.png" alt="Se répéter encore et encore" /></p>
<p>Le code s’exécuterait beaucoup plus vite si le JIT n’avait pas à répéter ces vérifications tout le temps. C’est une des choses que les compilations optimisées améliorent. Lors d’une compilation optimisée, la fonction est compilée comme un tout et la plupart des vérifications de type sont faites avant de lancer la boucle.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/02-10-jit_loop02.png" alt="Une stratégie plus intelligente pour faciliter l'analyse des types" /></p>
<p>Certains JIT vont même encore plus loin. Par exemple, dans Firefox, il existe un traitement spécial réservé aux tableaux d’entiers. Si <code>arr</code> est un tableau de ce genre, alors le JIT n’a plus besoin de vérifier si <code>arr[i]</code> est un entier. L’avantage c’est que le JIT peut alors se permettre de faire toutes les vérifications de type avant le démarrage de la boucle.</p>
<h2>Conclusion</h2>
<p>Voilà pour une présentation rapide du fonctionnement d’un JIT. Il permet d’exécuter JavaScript plus vite en observant le code pendant son exécution et en optimisant les parties de code les plus chaudes. À bien des égards cela a conduit à une amélioration significative des performances de JavaScript pour la plupart des applications.</p>
<p>Et pourtant, malgré ces améliorations les performances de JavaScript restent difficiles à prédire. En plus de ça, pour rendre les chose plus rapides, le JIT ajoute de la complexité notable lors de l’exécution. En particulier :</p>
<ul>
<li>L’optimisation et la dé-optimisation</li>
<li>L’augmentation de l’usage mémoire pour garder les informations du profileur et les informations nécessaires à le dé-optimisation</li>
<li>L’augmentation de l’usage mémoire nécessaire pour stocker les différentes versions compilées d’un même code.</li>
</ul>
<p>Il y a donc une marge de progression pour améliorer les choses : on pourrait supprimer cette complexité pour rendre les performances plus prédictibles. Et c’est justement une des choses que fait WebAssembly. Dans le prochain article, je rentrerai dans le détail de ce qu’est <a href="https://tech.mozfr.org/post/2017/03/08/Un-petit-cours-accelere-d-assembleur">l’assembleur et ce qu’en font les compilateurs</a>.</p>
<h2>À propos de <a href="http://twitter.com/linclark">Lin Clark</a></h2>
<p>Lin est ingénieure au sein de l’équipe Mozilla Developer Relations. Elle bidouille avec JavaScript, WebAssembly, Rust et Servo et crée des bandes dessinées sur le code.</p>
<p><style>
pre { white-space: pre;}
</style></p>
Un petit cours accéléré d'assembleururn:md5:7529e714d3f6a95c4cc76c07f48d3cb62017-03-08T18:53:00+01:002017-03-09T20:25:11+01:00sphinxJavaScriptJavaScriptWASMWebAssembly<p><em>Cet article est le troisième d’une série de traductions d’articles écrits par <a href="http://code-cartoons.com/">Lin Clark</a> et publiés sur le blog Hacks. <a href="https://hacks.mozilla.org/2017/02/a-crash-course-in-assembly/">La version anglaise est disponible ici</a>. Merci à Jeremie et à goofy et Benjamin pour la relecture :) Si vous n’avez pas lu les autres articles, nous vous conseillons de démarrer <a href="https://tech.mozfr.org/post/2017/03/08/Une-introduction-cartoonesque-a-WebAssembly">depuis le début</a>.</em></p>
<hr />
<p>Pour comprendre comment WebAssembly fonctionne, il peut être utile de comprendre ce qu’est l’assembleur et comment les compilateurs le produisent.</p>
<p>Dans <a href="https://tech.mozfr.org/post/2017/03/08/Un-petit-cours-accelere-de-compilation-a-la-volee-(JIT)">l’article sur la compilation à la volée (JIT)</a>, j’expliquais que communiquer avec une machine, c’était un peu comme communiquer avec un extraterrestre.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/03-01-alien03.png" alt="Discuter avec un alien" /></p>
<p>Nous allons maintenant voir comment ce cerveau extraterrestre fonctionne, comment la machine analyse et comprend ce qui lui est communiqué.</p>
<p>Une partie du cerveau est dédié à la réflexion (effectuer des additions, des soustractions, des opérations logiques). Il y a aussi non loin de là, une partie du cerveau qui fournit de la mémoire à court terme. Enfin, il y en a une dernière qui fournit de la mémoire à long terme.</p>
<p>Ces différentes parties ont chacune un nom :</p>
<ul>
<li>La partie dédiée à la réflexion est l’unité arithmétique et logique (UAL ou ALU en anglais).</li>
<li>La mémoire à court terme est fournie par les registres.</li>
<li>La mémoire à long terme est fournie par la mémoire vive (aussi appelée RAM en anglais pour <em>Random Access Memory</em>).</li>
</ul>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/03-02-computer_architecture09.png" alt="Un schéma avec la mémoire vive (RAM), les registres, le processeur (CPU) et l'AUL (ALU)" /></p>
<p>Les phrases formées par le code machine sont appelées des instructions.
Que se passe-t-il lorsqu’une de ces instructions parvient jusqu’au cerveau ? Elle est découpée en différentes parties qui ont chacune leur signification.</p>
<p>La façon dont cette instruction est découpée est propre au câblage de ce cerveau.
Ainsi, un cerveau câblé de cette façon prendrait toujours les six premiers bits pour les transmettre à l’UAL. L’UAL, en fonction de l’emplacement des zéros et des uns, comprendrait qu’il faut additionner deux trucs.</p>
<p>Ce morceau est appelé code de l’opération (ou « <em>opcode</em> » en anglais et dans le jargon informatique) car il indique à l’UAL l’opération qui doit être exécutée.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/03-03-computer_architecture12.png" alt="Comment l'opération est stockée dans l'UAL" /></p>
<p>Ensuite, le cerveau prend les deux prochains morceaux, de trois bits chacun, afin de déterminer les nombres qu’il faut additionner. Ce sont les adresses des registres à utiliser.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/03-04-computer_architecture17.png" alt="L'utilisation des registres dans l'opération" /></p>
<p>Vous voyez les annotations écrites au-dessus du code machine ? Elles sont ici pour nous aider, nous les humains, à mieux comprendre ce qui se passe. Ces annotations sont de l’assembleur. Ce sont des symboles mnémoniques qui permettent aux humains de donner du sens au code machine.</p>
<p>On peut voir ici qu’il existe une relation assez directe entre l’assembleur et le code machine de cette machine. À cause de cette relation, il existe différentes sortes d’assembleurs, chacun correspondant au type d’architecture d’une machine donnée. Lorsqu’on utilise une machine avec une architecture différente, il est fort probable qu’on ait besoin d’un autre « dialecte » d’assembleur.</p>
<p>Notre traduction ne vise donc pas une seule cible. Il n’existe pas de langue unique qui soit du code machine. Il existe différents codes machines. À l’instar de nous qui parlons différentes langues, les machines parlent différents codes.</p>
<p>Pour la traduction humain-extraterrestre, on pourrait partir de l’anglais, du russe ou du mandarin comme langue source et le traduire en langue extraterrestre A ou en langue extraterrestre B. En programmation, on peut poursuivre l’analogie en partant d’un programme écrit en C ou en C++ ou en Rust et vouloir le traduire en x86 ou en ARM.</p>
<p>On veut être capable de traduire depuis n’importe lequel de ces langages de programmation de haut niveau vers n’importe lequel de ces langages assembleurs (dont chacun correspond à une architecture différente). Une solution à ce problème serait de créer un ensemble de traducteurs qui permettent de passer de chaque langage de programmation à chaque langage assembleur.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/03-05-langs05.png" alt="Un échange impossible entre deux polyglottes ?" /></p>
<p>Ça se révèle plutôt inefficace. Pour résoudre ce problème, la plupart des compilateurs introduisent au moins une couche intermédiaire. Le compilateur prend en entrée le langage de programmation haut niveau et le traduit en quelque chose qui n’est ni un langage de haut niveau, ni du code machine. C’est ce qu’on appelle la représentation intermédiaire (RI ou IR en anglais).</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/03-06-langs06.png" alt="Un langage intermédiaire pour faciliter les échanges" /></p>
<p>Ça signifie que le compilateur peut prendre n’importe lequel de ces langages de haut niveau et le traduire dans un des langages de RI. À partir de là, un autre composant du compilateur peut traiter cette RI et la compiler en quelque chose de plus spécifique à l’architecture cible.</p>
<p>La partie frontale du compilateur traduit le langage de programmation de haut niveau en RI et la partie en arrière-plan traite cette RI pour la transformer en code assembleur pour l’architecture cible.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/03-07-langs09.png" alt="Chacun sa tâche : le front-end d'un côté et le back-end de l'autre" /></p>
<h2>Conclusion</h2>
<p>Voici ce qu’est l’assembleur et comment les compilateurs traduisent des langages de programmation de haut niveau en assembleur. Dans le prochain article, nous verrons <a href="https://tech.mozfr.org/post/2017/03/08/Creer-et-manipuler-des-modules-WebAssembly">comment WebAssembly s’inscrit dans cet ensemble</a>.</p>
<h2>À propos de <a href="http://twitter.com/linclark">Lin Clark</a></h2>
<p>Lin est ingénieure au sein de l’équipe Mozilla Developer Relations. Elle bidouille avec JavaScript, WebAssembly, Rust et Servo et crée des bandes dessinées sur le code.</p>
<p><style>
pre { white-space: pre;}
</style></p>
Créer et manipuler des modules WebAssemblyurn:md5:f81ad560c7f4548c1d6d522f0616a34a2017-03-08T18:52:00+01:002017-03-09T20:24:41+01:00sphinxJavaScriptJavaScriptWASMWebAssembly<p><em>Cet article est le quatrième d’une série de traductions d’articles écrits par <a href="http://code-cartoons.com/">Lin Clark</a> et publiés sur le blog Hacks. <a href="https://hacks.mozilla.org/2017/02/creating-and-working-with-webassembly-modules/">La version anglaise est disponible ici</a>. Merci à dattaz, Jeremie et à goofy et Benjamin pour la relecture :) Si vous n’avez pas lu les autres articles, nous vous conseillons de démarrer <a href="https://tech.mozfr.org/post/2017/03/08/Une-introduction-cartoonesque-a-WebAssembly">depuis le début</a>.</em></p>
<hr />
<p>WebAssembly est un outil permettant d’exécuter d’autres langages que JavaScript sur des pages web. Auparavant, lorsqu’on souhaitait exécuter du code dans le navigateur afin d’interagir avec les différents composants d’une page web, JavaScript était la seule solution.</p>
<p>C’est pourquoi, lorsqu’on dit que WebAssembly est rapide, on compare sa rapidité à celle de JavaScript. Cela ne signifie pas pour autant qu’il faut utiliser l’un ou l’autre et pas les deux.</p>
<p>En fait, on s’attend à ce que les développeurs utilisent aussi bien WebAssembly et JavaScript au sein de la même application. Même si vous n’écrivez pas du WebAssembly, vous pouvez en tirer parti.</p>
<p>Les modules WebAssembly définissent des fonctions qui peuvent être utilisées depuis JavaScript. Si aujourd’hui, vous téléchargez un module npm comme lodash et que vous utilisez les fonctions qu’il fournit via son API, demain, vous serez aussi capable de télécharger et d’exploiter des modules WebAssembly.</p>
<p>Voyons maintenant comment créer des modules WebAssembly et comment les utiliser depuis JavaScript.</p>
<h2>Quelle place pour WebAssembly ?</h2>
<p>Dans <a href="https://tech.mozfr.org/post/2017/03/08/Un-petit-cours-accelere-d-assembleur">l’article précédent sur l’assembleur</a>, nous avons vu comment les compilateurs traitaient les langages de programmation de haut niveau pour les traduire en code machine.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/04-01-langs09.png" alt="L'utilisation de la représentation intermédiaire avec les différents composants" /></p>
<p>Quel est le rôle de WebAssembly dans cet environnement ?</p>
<p>On peut penser qu’il s’agit simplement d’un autre langage assembleur vers lequel compiler. D’une certaine façon, c’est vrai mais chacun de ces autres langages (x86, ARM) correspond à une architecture machine particulière.</p>
<p>Lorsqu’on envoie du code à exécuter sur une machine à travers le Web, on ne connaît pas l’architecture cible sur laquelle le code sera exécuté.</p>
<p>WebAssembly est donc légèrement différent des autres langages assembleurs. Il s’agit d’un langage machine pour une machine théorique et non pour une machine physique.</p>
<p>Pour cette raison, les instructions WebAssembly sont parfois appelées instructions virtuelles. Elles sont beaucoup plus proches du code machine que n’importe quel code source JavaScript avec un langage qui ressemble à l’intersection de ce qui est effectué efficacement sur les architectures matérielles répandues. Mais ces instructions ne correspondent pas non plus à un langage machine spécifique d’une architecture matérielle donnée.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/04-02-langs08.png" alt="La place de WebAssembly par rapport à cette représentation intermédiaire" /></p>
<p>C’est le navigateur qui télécharge le code WebAssembly. Ensuite, il effectue la transition (plus courte) entre WebAssembly et le code assembleur de la machine sur laquelle il est exécuté.</p>
<h2>Compiler vers .wasm</h2>
<p>L’ensemble d’outils de compilation qui prend le mieux en charge WebAssembly actuellement s’appelle LLVM. Il existe différents environnements frontaux (<em>front-ends</em>) ou de fin de chaîne (<em>back-ends</em>) qui peuvent être utilisés avec LLVM.</p>
<p><em>Note : la plupart des développeurs de modules WebAssembly utiliseront des langages tels que C et Rust avant de compiler en WebAssembly. Toutefois, il existe d’autres méthodes qui permettent de créer des modules WebAssembly. Il existe par exemple <a href="https://github.com/rsms/wasm-util">un outil expérimental qui permet de compiler un module WebAssembly en utilisant TypeScript</a>. On peut aussi <a href="https://developer.mozilla.org/fr/docs/WebAssembly/Understanding_the_text_format">écrire directement du WebAssembly en utilisant sa représentation textuelle</a>.</em></p>
<p>Prenons le scénario où on développe un module en C pour le compiler en WebAssembly. On pourrait utiliser le module frontal clang pour passer de la représentation en C à la représentation intermédiaire LLVM. Une fois qu’on a obtenu la RI LLVM, LLVM peut la comprendre et effectuer certaines optimisations.</p>
<p>Pour passer de la RI (<a href="https://fr.wikipedia.org/wiki/Langage_interm%C3%A9diaire#Repr.C3.A9sentation_interm.C3.A9diaire">représentation intermédiaire</a>) LLVM à celle de WebAssembly, il nous faut un composant de fin de chaîne. Il existe un composant en cours de développement pour le projet LLVM. Ce composant devrait être finalisé sous peu mais reste délicat à utiliser aujourd’hui.</p>
<p>Il existe un autre outil, intitulé Emscripten, qui est actuellement plus facile à utiliser. Cet outil possède son propre composant de fin de chaîne qui peut produire du code WebAssembly en compilant vers une cible intermédiaire (appelée asm.js) puis en convertissant ce résultat en WebAssembly. Sous le capot d’Emscripten, on retrouve en fait LLVM et on peut donc passer d’un composant de fin de chaîne à l’autre à partir d’Emscripten.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/04-03-toolchain07.png" alt="L'ensemble d'outils de compilation" /></p>
<p>Emscripten inclut de nombreux outils et bibliothèques supplémentaires pour le portage de bases de code en C/C++. Il s’agit donc plus d’un kit de développement logiciel (NDT ou SDK pour <em>Software Developer Kit</em>, plus fréquemment utilisé) que d’un simple compilateur. Les développeurs système ont par exemple l’habitude d’utiliser un système de fichiers depuis lequel on peut lire des fichiers et sur lequel on peut en écrire. Pour ce faire, Emscripten peut simuler un système de fichier en utilisant IndexedDB.</p>
<p>Quels que soient les outils que vous utilisez, le résultat final sera un fichier dont l’extension sera .wasm. Nous verrons par la suite la structure d’un fichier .wasm mais pour commencer, voyons comment on peut l’utiliser en JavaScript.</p>
<h2>Charger un module WebAssembly en JavaScript</h2>
<p>Le fichier .wasm contient le module WebAssembly et peut être chargé en JavaScript. Au moment de l’écriture de ces lignes, le processus de chargement est un peu compliqué :</p>
<pre><code>function fetchAndInstantiate(url, importObject) {
return fetch(url).then(response =>
response.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, importObject)
).then(results =>
results.instance
);
}
</code></pre>
<p>Pour plus de détails, vous pouvez consulter <a href="https://developer.mozilla.org/en-US/docs/WebAssembly">la documentation associée</a>.</p>
<p>Nous travaillons à simplifier cette étape en améliorant les outils et en intégrant les modules WebAssembly dans des gestionnaires de modules comme webpack ou dans des outils de chargement comme SystemJS. Nous pensons que le chargement des modules WebAssembly peut être aussi simple que celui que nous connaissons aujourd’hui pour les modules JavaScript.</p>
<p>Il existe toutefois une différence fondamentale entre les modules WebAssembly et les modules JavaScript. Actuellement, les fonctions WebAssembly permettent uniquement d’utiliser des nombres (entiers ou flottants) comme paramètres et comme valeurs de retour.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/04-04-memory04.png" alt="On ne passe que des entiers" /></p>
<p>Pour manipuler des types de donnée plus complexes (des chaînes de caractères par exemple), il faut utiliser la mémoire du module WebAssembly.</p>
<p>Si vous travaillez principalement avec JavaScript, l’accès direct à la mémoire n’est pas forcément un concept très familier. Des langages de plus bas niveau tels que C, C++ ou Rust permettent de gérer la mémoire manuellement. La mémoire d’un module WebAssembly permet de simuler le tas (NDT ou « <em>heap</em> » en anglais, également usité) qu’on trouverait dans ces langages.</p>
<p>Pour cela, on utilise un type d’objet JavaScript : les <code>ArrayBuffer</code>. Un tableau tampon (NDT « <em>array buffer</em> » en anglais) est un tableau d’octets. Les indices des positions dans ce tableau servent d’adresses mémoire.</p>
<p>Si on veut passer une chaîne de caractères depuis le code JavaScript vers le code WebAssembly, on convertit les caractères en utilisant les codes d’encodage correspondants. Ensuite, on écrit ces codes dans le tableau représentant la mémoire. Les indices du tableau étant des entiers, on peut les passer à la fonction WebAssembly. Cela permet ainsi d’utiliser l’indice du première caractère de la chaîne comme un pointeur.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/04-05-memory12.png" alt="Pour compenser, on sert des entiers comme pointeurs" /></p>
<p>Il est probable que lorsque quelqu’un développera un module WebAssembly destiné à des développeurs web, il ajoutera une enveloppe (wrapper) avec des fonctions utilitaires pour ce module afin que le développeur web n’ait pas à se soucier de la gestion de la mémoire.</p>
<p>Si vous souhaitez en savoir plus, n’hésitez pas à consulter notre documentation à propos de la gestion de la mémoire en <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/WebAssembly/Memory">WebAssembly</a>.</p>
<h2>La structure d’un fichier .wasm</h2>
<p>Si vous écrivez du code avec un langage de programmation de haut niveau pour le compiler en WebAssembly, vous n’avez pas besoin de savoir quelle est la structure d’un module WebAssembly. Ceci étant dit, comprendre les notions de base s’avère souvent utile.</p>
<p>Si ce n’est pas déjà fait, nous vous conseillons de lire <a href="https://tech.mozfr.org/post/2017/03/08/Un-petit-cours-accelere-d-assembleur">l’article précédent sur l’assembleur (le troisième de cette série)</a>.</p>
<p>Voici une fonction, écrite en C, que nous allons transformer en WebAssembly :</p>
<pre><code>int add42(int num) {
return num + 42;
}
</code></pre>
<p>Vous pouvez essayer d’utiliser <a href="http://mbebenita.github.io/WasmExplorer/">WASM Explorer</a> afin de compiler cette fonction.</p>
<p>Si vous ouvrez le fichier .wasm obtenu (et que votre éditeur le permet), vous verrez alors quelque chose comme :</p>
<pre><code>00 61 73 6D 0D 00 00 00 01 86 80 80 80 00 01 60
01 7F 01 7F 03 82 80 80 80 00 01 00 04 84 80 80
80 00 01 70 00 00 05 83 80 80 80 00 01 00 01 06
81 80 80 80 00 00 07 96 80 80 80 00 02 06 6D 65
6D 6F 72 79 02 00 09 5F 5A 35 61 64 64 34 32 69
00 00 0A 8D 80 80 80 00 01 87 80 80 80 00 00 20
00 41 2A 6A 0B
</code></pre>
<p>Ce qu’on voit ici est la représentation « binaire » du module (avec des guillemets de précaution car généralement, le contenu est affiché en notation hexadécimale, mais on peut facilement la convertir en notation binaire ou dans un format plus lisible pour un humain).</p>
<p>Voici par exemple à quoi ressemble <code>num + 42</code> :</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/04-06-hex_binary_asm01.png" alt="La décomposition de l'opération selon les différents niveaux de représentation" /></p>
<h3>Le fonctionnement du code : un processeur à pile</h3>
<p>Au cas où vous vous demanderiez, voici ce que feraient ces instructions :</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/04-07-hex_binary_asm02.png" alt="La décomposition de l'opération selon les différents niveaux de représentation" /></p>
<ul>
<li>On prend la valeur du premier paramètre et on la met sur la pile.</li>
<li>On met une valeur constante sur la pile</li>
<li>On prend les deux valeurs sur le haut de la pile, on les additionne et on met le résultat sur la pile.</li>
</ul>
<p>On peut voir ici que l’opération <code>add</code> n’indique pas l’origine des valeurs qu’elle manipule. En effet, WebAssembly est ce qu’on appelle un automate à pile. Cela signifie que les valeurs nécessaires à une opération sont empilées avant que l’opération soit appliquée.</p>
<p>Pour l’addition, WebAssembly sait combien de valeurs sont nécessaires. L’addition a besoin de deux valeurs et on prend donc les deux valeurs situées sur le haut de la pile. Cela signifie que l’instruction pour l’addition peut être courte (un seul octet) car il n’est pas nécessaire d’indiquer les registres de source ou de destination. Cela permet de réduire la taille du fichier .wasm et ainsi de réduire le temps nécessaire à son téléchargement.</p>
<p>Bien que WebAssembly soit conçu comme un automate à pile, ce n’est pas comme ça qu’il fonctionne réellement sur la machine physique. Lorsque le navigateur traduit le code WebAssembly en code machine pour l’architecture sur laquelle il est exécuté, le code utilisera les registres. Étant donné que le code WebAssembly ne détaille pas les registres, cela fournit une plus grande flexibilité au navigateur qui peut choisir la meilleure stratégie d’allocation des registres pour la machine utilisée.</p>
<h3>Les sections du module</h3>
<p>En plus de la fonction add42, on trouve d’autres parties dans le fichier .wasm. Ces parties sont appelées des « sections ». Certaines de ces sections sont nécessaires quel que soit le module et d’autres sont optionnelles.</p>
<p>Voici la liste des sections obligatoires :</p>
<ol>
<li><strong>Type</strong> : cette section contient la signature des fonctions qui sont définies dans ce module ou importées.</li>
<li><strong>Function</strong> : cette section contient un index de chaque fonction qui est définie dans ce module.</li>
<li><strong>Code</strong> : cette section contient le corps de chaque fonction définie dans ce module.</li>
</ol>
<p>Voici la liste des sections optionnelles :</p>
<ol>
<li><strong>Export</strong> : cette section permet de rendre accessibles la mémoire, les tables et les variables globales pour d’autres modules WebAssembly et pour JavaScript. Cela permet d’avoir des modules compilés séparément et de les lier dynamiquement. C’est en quelque sorte la version WebAssembly d’une .dll</li>
<li><strong>Import</strong> : cette section définit les fonctions, mémoires, tables et variables globales qui doivent être importées depuis d’autres modules WebAssembly ou depuis du JavaScript.</li>
<li><strong>Start</strong> : une fonction qui sera automatiquement exécutée au chargement du module WebAssembly (l’équivalent d’une fonction main)</li>
<li><strong>Global</strong> : cette section définit les variables globales du module.</li>
<li><strong>Memory</strong> : cette section définit la mémoire utilisée par ce module.</li>
<li><strong>Table</strong> : cette section permet de faire un pont avec des fonctions situées en dehors du module WebAssembly telles que des fonctions JavaScript. Cela est notamment utile pour permettre des appels de fonction indirects.</li>
<li><strong>Data</strong> : cette section initialise la mémoire locale ou importée.</li>
<li><strong>Element</strong> : cette section initialise une table locale ou importée.</li>
</ol>
<p>Pour plus de détails quant au fonctionnement des sections, vous trouverez <a href="https://rsms.me/wasm-intro">plus d’explications dans la documentation</a>.</p>
<h2>La suite</h2>
<p>Maintenant qu’on sait comment fonctionnent les modules WebAssembly, voyons <a href="https://tech.mozfr.org/post/2017/03/08/D-ou-vient-la-rapidite-de-WebAssembly">pourquoi WebAssembly est rapide</a>.</p>
<h2>À propos de <a href="http://twitter.com/linclark">Lin Clark</a></h2>
<p>Lin est ingénieure au sein de l’équipe Mozilla Developer Relations. Elle bidouille avec JavaScript, WebAssembly, Rust et Servo et crée des bandes dessinées sur le code.</p>
<p><style>
pre { white-space: pre;}
</style></p>
D'où vient la rapidité de WebAssembly ?urn:md5:93bcf99b0ae17807405caedc30f0d3d22017-03-08T18:51:00+01:002017-03-09T20:24:21+01:00sphinxJavaScriptJavaScriptWASMWebAssembly<p><em>Cet article est le cinquième d’une série de traductions d’articles écrits par <a href="http://code-cartoons.com/">Lin Clark</a> et publiés sur le blog Hacks. <a href="https://hacks.mozilla.org/2017/02/what-makes-webassembly-fast/">La version anglaise est disponible ici</a>. Merci à goofy et Benjamin pour la relecture :) Si vous n’avez pas lu les autres articles, nous vous conseillons de démarrer <a href="https://tech.mozfr.org/post/2017/03/08/Une-introduction-cartoonesque-a-WebAssembly">depuis le début</a>.</em></p>
<hr />
<p>Dans <a href="https://tech.mozfr.org/post/2017/03/08/Creer-et-manipuler-des-modules-WebAssembly">l’article précédent</a>, nous avons vu que la programmation avec WebAssembly et la programmation en JavaScript ne s’excluaient pas mutuellement. Nous ne pensons pas qu’il y aura beaucoup de développeurs qui écriront des bases de code complètes en WebAssembly.</p>
<p>Les développeurs n’ont donc pas à choisir entre WebAssembly et JavaScript pour développer leurs applications. Toutefois, nous pensons que les développeurs échangeront certaines parties du code JavaScript pour des modules WebAssembly.</p>
<p>Ainsi, l’équipe qui travaille sur React pourrait remplacer le code du DOM virtuel avec une version WebAssembly. Cela n’aurait aucun impact pour les personnes qui utilisent React. Leurs applications continueraient de fonctionner comme avant, tout en bénéficiant des avantages de WebAssembly.</p>
<p>Pourquoi les développeurs de React passeraient-ils cette partie du code sur un composant WebAssembly ? Parce que WebAssembly est plus rapide. Certes… mais pourquoi est-il plus rapide ?</p>
<h2>Quel est l’état actuel des performances de JavaScript ?</h2>
<p>Avant de pouvoir comprendre les différences de performance entre JavaScript et WebAssembly, il faut comprendre comment fonctionne un moteur JavaScript.
Ce diagramme dresse un rapide tableau des performances actuellement observées au démarrage d’une application.</p>
<p><em>Le temps consommé par le moteur JavaScript pour chacune de ces pages dépend du code JavaScript de la page. Ce diagramme n’a pas pour but d’indiquer des mesures de performance précises et chiffrées, mais de fournir un modèle général pour comparer les performances de JavaScript et celles de WebAssembly sur une phase analogue.</em></p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/05-01-diagram_now01.png" alt="Les différentes tâches à réaliser pour lancer une application JavaScript" /></p>
<p>Chaque barre indique le temps consommé pour une tâche donnée.</p>
<ul>
<li>Analyse (<em>parsing</em>) : le temps nécessaire pour analyser le code et le transformer en quelque chose qui puisse être exécuté par l’interpréteur.</li>
<li>Compilation et optimisation : le temps consommé par le compilateur et l’optimiseur. Certaines des tâches d’optimisation ne sont pas exécutées sur le thread principal, le temps correspondant n’est pas inclus ici.</li>
<li>Ré-optimisation : le temps que passe le compilateur à la volée (JIT) à réajuster les hypothèses incorrectes, optimiser le code à nouveau et rediriger l’exécution vers un code moins optimisé.</li>
<li>Exécution : le temps nécessaire à l’exécution du code.</li>
<li>Ramasse-miettes : le temps passé à nettoyer la mémoire.</li>
</ul>
<p>Une chose importante à noter : ces tâches ne forment pas chacune un bloc distinct et elles ne s’exécutent pas non plus dans un ordre bien défini. On a plutôt des tâches qui se recoupent, un peu d’analyse puis de l’exécution, puis de la compilation et encore de l’analyse et ensuite de l’exécution, etc.</p>
<p>Cette décomposition représente une avancée fondamentale par rapport aux débuts de JavaScript où on avait plutôt quelque chose comme :</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/05-02-diagram_past01.png" alt="L'enchaînement n'est pas si linéaire" /></p>
<p>Au début, lorsqu’il y avait uniquement un interpréteur qui exécutait le code JavaScript, la phase d’exécution était plutôt lente. Lorsque les compilateurs à la volée sont apparus, cela a fortement réduit le temps d’exécution.</p>
<p>Le prix à payer est qu’il faut désormais surveiller et compiler le code. Si les développeurs avaient continué à développer du JavaScript sur des projets de tailles analogues, les temps d’analyse et de compilation seraient très courts, mais ces améliorations de performance ont conduit les développeurs à créer des applications plus vastes. Il y a donc encore de la marge pour des améliorations.</p>
<h2>Et WebAssembly alors ?</h2>
<p>Voici une approximation qui illustre comment WebAssembly se comporterait pour une application web typique.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/05-03-diagram_future01.png" alt="JS contre WASM, qui est le plus rapide ?" /></p>
<p>Il existe quelques variations entre les navigateurs pour ces différentes phases. Ici, j’utilise SpiderMonkey (NDT le moteur JavaScript de Firefox) comme modèle.</p>
<h2>Le téléchargement (<em>fetching</em>)</h2>
<p>Cela n’est pas montré dans le diagramme mais lorsqu’on télécharge le fichier depuis le serveur, cela prend également du temps.</p>
<p>WebAssembly étant plus compact que JavaScript, la récupération des fichiers est plus rapide. Bien que les algorithmes de compression puissent drastiquement réduire la taille d’un paquet de ressources JavaScript, la représentation binaire compressée d’un code WebAssembly sera tout de même plus légère.</p>
<p>Cela signifie qu’il faut moins de temps pour transférer les ressources depuis le serveur vers le client, notamment pour les connexions avec un débit moins élevé.</p>
<h2>L’analyse (<em>parsing</em>)</h2>
<p>Une fois que le navigateur a récupéré le fichier, le code source JavaScript est analysé afin de créer un arbre syntaxique abstrait (NDT <em>Abstract Syntax Tree</em> ou AST en anglais).</p>
<p>Les navigateurs effectuent cette analyse uniquement lorsqu’ils en ont besoin et se contentent de créer des points de références (ou <em>stubs</em>) pour les fonctions qui n’ont pas encore été appelées. À partir de cette étape, l’arbre syntaxique abstrait est converti en une représentation intermédiaire (aussi appelée <em>bytecode</em>) qui est spécifique au moteur JavaScript.</p>
<p>En comparaison, WebAssembly n’a pas besoin de cette phase de transformation, car il s’agit déjà d’une représentation intermédiaire. Il suffit qu’il soit décodé et validé pour vérifier qu’il ne contient pas d’erreur.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/05-04-diagram_compare02.png" alt="Comparaison approximative entre JS et WASM pour l'analyse (parsing)" /></p>
<h2>La compilation et l’optimisation</h2>
<p>Comme nous l’avons vu dans l’article sur <a href="https://tech.mozfr.org/post/2017/03/08/Un-petit-cours-accelere-de-compilation-a-la-volee-(JIT)">les compilateurs à la volée (JIT)</a>, JavaScript est compilé pendant l’exécution du code. Selon les types utilisés pendant l’exécution, on peut avoir plusieurs versions du même code qui ont besoin d’être compilées.</p>
<p>Les différents navigateurs ont chacun leur approche pour compiler du code WebAssembly. Certains navigateurs lancent une compilation minimale du code WebAssembly avant de l’exécuter, d’autres utilisent une compilation à la volée.</p>
<p>Dans tous les cas, à l’état initial, WebAssembly est déjà beaucoup plus proche du code machine. Les types de données font par exemple partie du programme. Cette phase est plus rapide pour plusieurs raisons :</p>
<ul>
<li>Le compilateur n’a pas besoin de passer du temps à exécuter le code pour surveiller les types à utiliser avant de commencer à compiler un code optimisé.</li>
<li>Le compilateur n’a pas besoin de compiler différentes versions du même code selon les différents types observés.</li>
<li>Des optimisations ont déjà été appliquées en amont par LLVM. Il faut donc moins de travail pour la compilation et l’optimisation.</li>
</ul>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/05-05-diagram_compare03.png" alt="Comparaison approximative entre JS et WASM pour la compilation et l'optimisation" /></p>
<h2>La deuxième passe d’optimisation</h2>
<p>Il arrive parfois que le compilateur à la volée doive rejeter une version du code pour l’observer de nouveau.</p>
<p>Cela se produit lorsque les hypothèses utilisées par le compilateur à la volée selon le code exécuté s’avèrent incorrectes. C’est par exemple le cas lorsque des variables utilisées dans une boucle sont différentes par rapport aux itérations précédentes ou lorsqu’une nouvelle fonction est insérée dans la chaîne de prototypes.</p>
<p>Cela consomme du temps pour deux raisons. Premièrement, il faut repasser du code optimisé au code de base, ce qui prend du temps. Deuxièmement, si une fonction continue d’être appelée fréquemment, le compilateur à la volée peut choisir de la passer à nouveau à l’optimiseur : on a alors le coût en temps d’une deuxième compilation.</p>
<p>En WebAssembly, les paramètres tels que les types sont explicites. Le compilateur à la volée n’a donc pas besoin d’émettre des hypothèses sur les types à partir des données récupérées pendant l’exécution. Cela signifie qu’il n’est pas nécessaire de passer par ces cycles de ré-optimisation.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/05-06-diagram_compare04.png" alt="Comparaison approximative entre JS et WASM pour la deuxième passe d'optimisation" /></p>
<h2>L’exécution</h2>
<p>Il est tout à fait possible d’écrire du JavaScript qui est exécuté de façon performante. Pour cela, il est nécessaire de connaître les optimisations qui sont réalisées par le compilateur à la volée. Il faut par exemple savoir comment écrire du code afin que le compilateur puisse opérer une spécialisation de type (cf. <a href="https://tech.mozfr.org/post/2017/03/08/Un-petit-cours-accelere-de-compilation-a-la-volee-(JIT)">l’article sur la compilation JIT</a>).</p>
<p>Cependant, la plupart des développeurs ne connaissent pas ces détails de compilation. Et même pour les développeurs qui connaissent ces notions, obtenir le bon équilibre est parfois difficile. Certaines méthodes utilisées pour rendre le code plus lisible (comme créer des tâches abstraites génériques qui fonctionnent quel que soit le type utilisé) vont à l’encontre du compilateur lorsqu’il s’agit d’optimiser le code.</p>
<p>De plus, les optimisations utilisées par un compilateur JIT varient d’un navigateur à l’autre et développer « pour » un navigateur donné peut ne pas avoir l’effet escompté voire l’effet inverse…</p>
<p>Étant donné ces différentes raisons, l’exécution de code WebAssembly est généralement plus rapide. La plupart des optimisations réalisées par le compilateur à la volée pour JavaScript (comme la spécialisation de type) ne sont pas nécessaires pour WebAssembly.</p>
<p>En outre, WebAssembly a été conçu comme une cible de compilation. Cela signifie qu’il a été conçu pour être généré par des compilateurs et pas pour être écrit par des humains.</p>
<p>Les développeurs n’ayant pas besoin de programmer directement en WebAssembly, celui-ci peut utiliser un ensemble d’instructions plus adaptées aux machines. Selon la tâche réalisée par votre code, ces instructions peuvent s’exécuter 10 % à 800 % plus rapidement.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/05-07-diagram_compare05.png" alt="Comparaison approximative entre JS et WASM pour l'exécution" /></p>
<h2>La gestion du ramasse-miettes</h2>
<p>En JavaScript, le développeur n’a pas à se soucier de la mémoire utilisée par des variables devenues inutiles. C’est le moteur JavaScript qui s’occupe automatiquement de cette tâche grâce à ce qu’on appelle un ramasse-miettes.</p>
<p>Cela peut toutefois poser problème si on souhaite avoir des performances prédictibles. On ne maîtrise pas le moment où le ramasse-miettes sera actif et ça peut très bien être au mauvais moment. La plupart des navigateurs sont désormais assez affutés pour déclencher le ramasse-miettes quand il faut mais cela représente toujours une dépense de ressources et de temps qui peut ralentir l’exécution du code.</p>
<p>À l’heure actuelle, WebAssembly fonctionne sans aucun ramasse-miettes. La mémoire doit être gérée manuellement (comme c’est le cas avec des langages comme C ou C++). Bien que cela rende le développement plus complexe, cela permet également d’obtenir des performances plus stables.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/05-08-diagram_compare06.png" alt="Comparaison approximative entre JS et WASM pour la gestion du ramasse-miettes" /></p>
<h2>Conclusion</h2>
<p>À de nombreux égards, WebAssembly est plus rapide que JavaScript :</p>
<ul>
<li>La récupération des ressources WebAssembly prend moins de temps, car le code WebAssembly est plus compact que le code JavaScript même lorsque ce dernier est compressé.</li>
<li>Le décodage du code WebAssembly prend moins de temps que l’analyse syntaxique du code JavaScript.</li>
<li>La compilation et l’optimisation du code WebAssembly prend moins de temps car celui-ci est plus proche du code machine et a déjà subi certaines optimisations du générateur de code wasm (par ex. LLVM) en amont.</li>
<li>Les passes d’optimisation successives ne sont pas nécessaires en WebAssembly, car les types et les autres informations font partie du code. Le moteur JavaScript n’a donc pas besoin d’émettre des hypothèses comme il le fait pour du code JavaScript classique.</li>
<li>L’exécution est généralement plus rapide, car il y a moins d’astuces/pièges à connaître pour écrire du code qui soit cohérent et performant. De plus l’ensemble des instructions WebAssembly est plus adapté aux machines.</li>
<li>Le ramasse-miettes n’est pas utilisé avec WebAssembly, car la mémoire est gérée manuellement.</li>
</ul>
<p>C’est pour ces différentes raisons que dans de nombreux cas, WebAssembly sera plus performant que JavaScript pour réaliser une même tâche.</p>
<p>Il existe certains cas où WebAssembly n’est pas aussi performant qu’il devrait l’être. Certains changements sont également en cours pour rendre WebAssembly plus rapide. C’est ce que nous verrons <a href="https://tech.mozfr.org/post/2017/03/08/WebAssembly-aujourd-hui-et-demain">dans le prochain article</a>.</p>
<h2>À propos de <a href="http://twitter.com/linclark">Lin Clark</a></h2>
<p>Lin est ingénieure au sein de l’équipe Mozilla Developer Relations. Elle bidouille avec JavaScript, WebAssembly, Rust et Servo et crée des bandes dessinées sur le code.</p>
<p><style>
pre { white-space: pre;}
</style></p>
WebAssembly aujourd'hui et demainurn:md5:cf9cbb2c0d5fe4495c4e9a24a6883add2017-03-08T18:50:00+01:002017-03-09T20:23:26+01:00sphinxJavaScriptJavaScriptWASMWebAssembly<p><em>Cet article est le sixième d’une série de traductions d’articles écrits par <a href="http://code-cartoons.com/">Lin Clark</a> et publiés sur le blog Hacks. <a href="https://hacks.mozilla.org/2017/02/where-is-webassembly-now-and-whats-next/">La version anglaise est disponible ici</a>. Merci à dattaz et à goofy et Benjamin pour la relecture :) Si vous n’avez pas encore lu les autres articles, nous vous recommandons de commencer <a href="https://tech.mozfr.org/post/2017/03/08/Une-introduction-cartoonesque-a-WebAssembly">depuis le début</a>.</em></p>
<hr />
<p>Le 28 février, <a href="https://lists.w3.org/Archives/Public/public-webassembly/2017Feb/0002.html">les quatre navigateurs principaux ont annoncé leur consensus</a> sur le fait que WebAssembly était suffisamment avancé pour fournir un produit viable. Ceci fournit une version initiale stable que les navigateurs peuvent implémenter et mettre à disposition.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/06-01-logo_party01.png" alt="Les différents navigateurs sont d'accord pour WASM" /></p>
<p>Ceci fournit un noyau stable que les navigateurs peuvent rendre disponible. Ce noyau ne contient pas toutes les fonctionnalités prévues par le groupe communautaire, mais il en contient suffisamment pour que WebAssembly soit rapide et utilisable.</p>
<p>Avec ceci, les développeurs peuvent commencer à diffuser du code WebAssembly. Pour les versions antérieures des navigateurs, les développeurs peuvent fournir une version asm.js du code. asm.js étant un sous-ensemble de JavaScript, tout moteur JavaScript pourra exécuter ce code. Avec Emscripten, vous pouvez compiler la même application vers WebAssembly et vers asm.js</p>
<p>Même dans cette version initiale, WebAssembly sera rapide. Il devrait devenir plus rapide à l’avenir, grâce à un ensemble de corrections et de nouvelles fonctionnalités.</p>
<h2>Améliorer les performances de WebAssembly dans les navigateurs</h2>
<p>Certaines améliorations de vitesse viendront au fur et à mesure que les navigateurs amélioreront la prise en charge de WebAssembly dans leurs moteurs. Les fournisseurs de navigateurs travaillent sur ces différents problèmes de manière indépendante.</p>
<h3>Des appels de fonction plus rapides entre JavaScript et WebAssembly</h3>
<p>Actuellement, appeler une fonction WebAssembly dans du code JavaScript est plus lent que ce qu’on pourrait espérer. C’est à cause de ce qu’on appelle le « trampolinage ». Le compilateur JIT ne sait pas comment interagir directement avec WebAssembly et il doit donc rediriger le code WebAssembly vers quelque chose qui sait le faire. Le composant en question est un code lent dans le moteur et qui prépare à exécuter le code WebAssembly optimisé.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/06-02-trampoline01.png" alt="Vers l'infini et WebAssembly (oui je sors)" /></p>
<p>Cette étape peut être jusqu’à 100 fois plus lente que ce qu’on aurait obtenu si le compilateur JIT savait comment l’interpréter directement.</p>
<p>Vous ne remarquez pas ce délai si vous passez une seule tâche conséquente au module WebAssembly. En revanche, si vous avez de nombreux allers-retours entre WebAssembly et JavaScript (comme lorsqu’on effectue de petites tâches), ce délai sera remarquable.</p>
<h3>Un temps de chargement plus rapide</h3>
<p>Les compilateurs à la volée (JIT) doivent négocier un compromis entre des temps de chargement plus rapides et des temps d’exécution plus rapides. Si on passe plus de temps à compiler et optimiser le code en amont, cela accélèrera l’exécution mais ralentira le démarrage du programme.</p>
<p>De nombreux travaux sont en cours pour améliorer cet équilibre entre la compilation en amont (qui s’assure qu’il n’y a pas de ralentissement lorsque le code a démarré son exécution) et l’hypothèse simple comme quoi la plupart du code ne sera pas exécuté suffisamment pour que l’optimisation soit rentable.</p>
<p>Puisque WebAssembly n’a pas besoin de spéculer sur les types qui seront utilisés, les moteurs n’ont pas à surveiller les types manipulés lors de l’exécution. Délestés de cette tâche, ils peuvent faire autre chose et notamment compiler et exécuter le code en parallèle.</p>
<p>De plus, de récents ajouts à l’API JavaScript permettront d’effectuer une compilation au fil de l’eau (en utilisant des <em>streams</em>). Cela signifie que le moteur pourra commencer la compilation alors que le module est toujours en cours de téléchargement.</p>
<p>Dans Firefox, nous travaillons sur un système à deux compilateurs. Un premier compilateur est exécuté en amont et effectue une première optimisation du code, plutôt efficace. Pendant que ce code est exécuté, un deuxième compilateur effectue une optimisation poussée en arrière-plan. Lorsque la version pleinement optimisée est disponible, on bascule l’exécution sur cette version.</p>
<h2>Ajouter des fonctionnalités à la spécification après cette phase initiale</h2>
<p>Un des objectifs de WebAssembly est de construire la spécification au fur et à mesure, par petits morceaux, plutôt que de concevoir tout d’un bloc en amont.</p>
<p>Cela signifie qu’on attend de nombreuses fonctionnalités mais qu’elles n’ont pas encore été complètement conçues. Elles devront passer par une phase de spécification dans laquelle interviennent tous les fournisseurs de navigateur.</p>
<p>Ces fonctionnalités sont intitulées « fonctionnalités futures ». En voici quelques-unes.</p>
<h3>Manipuler le DOM directement</h3>
<p>Actuellement, il n’existe aucun moyen qui permette d’interagir avec le DOM. Cela signifie que depuis WebAssembly, on ne peut pas utiliser un objet comme <code>element.innerHTML</code> pour mettre à jour un nœud du document.</p>
<p>En l’état, il faut passer par JavaScript pour définir la valeur. Cela signifie qu’il faut transmettre une valeur au code JavaScript qui a appelé le module WebAssembly ou qu’il faut appeler une fonction JavaScript depuis le code WebAssembly (un module WebAssembly pouvant importer des fonctions WebAssembly et des fonctions JavaScript).</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/06-03-dom01.png" alt="Écrire sur le DOM sans passer par le JavaScript" /></p>
<p>Dans tous les cas, utiliser JavaScript comme intermédiaire sera plus lent qu’un accès direct. Certains champs d’application de WebAssembly devront peut-être attendre que ce point soit résolu.</p>
<h3>Un accès concurrent à la mémoire partagée</h3>
<p>Une méthode pour accélérer le code consiste à exécuter différentes parties du code en même temps, en parallèle. Cette approche réserve parfois de mauvaises surprises, car la communication entre les threads peut nécessiter plus de temps qu’il n’aurait fallu pour exécuter la même tâche de façon classique.</p>
<p>Mais lorsqu’il est possible de partager la mémoire entre les threads, ce délai est réduit. Pour cela, WebAssembly utilisera les nouveaux objets <code>SharedArrayBuffer</code> de JavaScript. Une fois que ce type d’objet sera implémenté dans les navigateurs, le groupe de travail pourra définir la façon dont WebAssembly fonctionne avec <code>SharedArrayBuffer</code>.</p>
<h3>SIMD</h3>
<p>Si vous avez lu d’autres billets ou regardé des présentations sur WebAssembly, vous avez pu entendre parler de SIMD. Cet acronyme signifie <em>Single Instruction Multiple Data</em> (NDT pour « instruction unique, données multiples »). C’est une autre méthode pour exécuter des tâches en parallèle.</p>
<p>SIMD permet de traiter de grands ensembles de données (un vecteur de différents nombres par exemple) et d’appliquer en même temps la même instruction aux différentes parties de cet ensemble. Grâce à cet outil, on peut accélérer de façon drastique certains calculs complexes nécessaires pour les jeux ou la réalité virtuelle.</p>
<p>Cette avancée n’est pas primordiale pour les développeurs d’applications web classiques, mais elle est cruciale pour les développeurs qui travaillent sur des applications multimédia comme les jeux vidéos.</p>
<h3>La gestion des exceptions</h3>
<p>De nombreuses bases de code écrites dans des langages tels que C++ utilisent les exceptions. Cependant, les exceptions ne font pas encore partie de la spécification WebAssembly.
Si vous compilez votre code avec Emscripten, celui-ci émulera la gestion des exceptions pour certains niveaux d’optimisation. Cependant, cette émulation reste plutôt lente et si vous souhaitez la désactiver, vous pouvez utiliser l’option de compilation <a href="https://kripken.github.io/emscripten-site/docs/optimizing/Optimizing-Code.html#c-exceptions"><code>DISABLE_EXCEPTION_CATCHING</code></a>.</p>
<p>Lorsque les exceptions seront gérées nativement par WebAssembly, cette émulation ne sera plus nécessaire.</p>
<h3>D’autres pistes d’amélioration : simplifier le travail des développeurs.</h3>
<p>Certaines fonctionnalités à venir n’auront pas d’impact sur les performances mais faciliteront la tâche aux développeurs qui travaillent avec WebAssembly :</p>
<ul>
<li><strong>Des outils de développement de premier rang</strong> pour interagir avec le code source. À l’heure actuelle, déboguer du code WebAssembly dans le navigateur ressemble un peu à déboguer de l’assembleur brut. Très peu de développeurs peuvent faire le lien mental entre le code source et l’assembleur obtenu. Nous cherchons à améliorer les outils disponibles afin que les développeurs puissent déboguer leur code source.</li>
<li><strong>Intégration du ramasse-miettes.</strong> Si vous pouvez définir les types en amont, vous devriez pouvoir transformer votre code en WebAssembly. Du code écrit avec un langage tel que TypeScript devrait donc être compilable en WebAssembly. Le seuil écueil qui subsiste est que WebAssembly ne sait pas comment interagir avec les ramasse-miettes existants tels que ceux construits dans les moteurs JavaScript. L’idée de cette fonctionnalité est de permettre à WebAssembly d’accéder au ramasse-miettes natif grâce à un ensemble de types et d’opérations de bas niveau qui sont reliées au ramasse-miettes.</li>
<li><strong>Intégration des modules ES6.</strong> Les navigateurs sont en train d’ajouter la prise en charge du chargement des modules JavaScript grâce à la balise <code>script</code>. Une fois cette fonctionnalité ajoutée, une balise comme <code><script src=url type="module"></code> fonctionnera, même si l’URL pointe vers un module WebAssembly.</li>
</ul>
<h2>Conclusion</h2>
<p>WebAssembly est rapide aujourd’hui et avec les nouvelles fonctionnalités et améliorations des implémentations dans les navigateurs, il devrait devenir encore plus rapide</p>
<h2>À propos de <a href="http://twitter.com/linclark">Lin Clark</a></h2>
<p>Lin est ingénieure au sein de l’équipe Mozilla Developer Relations. Elle bidouille avec JavaScript, WebAssembly, Rust et Servo et crée des bandes dessinées sur le code.</p>
<p><style>
pre { white-space: pre;}
</style></p>