Introduction au WebAssembly

Salut !

Si on discutait un peu de WebAssembly aujourd’hui ? J’ai très envie d’en parler parce que le concept a un potentiel monstrueux et qu’en plus c’est déjà une norme du web depuis 2019.

Posons-nous un peu. Enormément de programmes (avec un big up pour les jeux vidéos) sont codés en C/C++. Le Javascript est écrit en C++. Sublime Text est en C++. Les moteurs graphiques sont en C++. Pourquoi ? Parce qu’il faut bien se dire que même si le langage est complexe et très bas niveau, c’est tout de même une bombe de puissance niveau performances (tant que c’est correctement écrit mais ça s’applique à tous les langages…). Le Rust fait également doucement son trou, certains et certaines le considérant comme étant le digne héritier de C/C++. De l’autre côté, les applications web s’imposent comme étant aujourd’hui un incontournable avec toutes les technologies géniales qui émergent autant en front que en back, avec l’explosion du Javascript, du NodeJS, etc.

Le souci, c’est que le C/C++ ou le Rust ne sont pas des langages web, et que Javascript ne les dépassera pas niveau performance ou sécurité sur les developpements (typage fort etc). Et si..et si on arrivait à combiner les deux ? Mesdames, mesdemoiselles, messieurs, chers lecteurs, je vous présente le WebAssembly.

Présentation du WebAssembly

WebAssembly c’est avant tout un langage pouvant être éxecuté dans les navigateurs modernes (IE non compris on s’entend). Il correspond à un bytecode (fichier binaire) obtenu après compilation en WebAssembly d’un langage tier, principalement C/C++ ou Rust mais il en existe d’autres. Comme le bytecode est particulièrement bas niveau, cela permet aux navigateurs d’accéder à des performances très élevées.

Plus concrètement, les applications web vont encapsuler via Javascript un fichier .wasm contenant le bytecode qui sera compilé en langage machine en temps réel au fur et à mesure de sa lecture. Rien que ça, ça en jette. Ils appellent cela la Streaming Compilation. Le module WebAssembly va exposer une fonction que Javascript pourra alors utiliser. Ce qui est génial c’est l’avancée que cela représente par rapport à l’ancien existant, je m’explique :

Sans parler de WebAssembly, on sait compiler un fichier C ou Rust en JS mais le JS lui est compilé en just-in-time pour obtenir le langage machine (dans le cas des navigateurs modernes). Cette compilation just-in-time ne peut donc pas commencer tant que les fichiers externes n’ont pas été entièrement téléchargés. C’est le drame niveau performances et on perd tout l’interêt du JIT.

La Streaming Compilation, elle, permet de compiler le bytecode WebAssembly en langage machine au fur et à mesure de la lecture des octets. C’est donc du temps réel et une amélioration très importante des performances et des temps de démarrage. De cette façon, il est possible de faire tourner dans le navigateur du code autre que Javascript à une vitesse proche de celle du processeur.

Schéma

Il y a plusieurs façon d’utiliser le WebAssembly. Comme je le disais plus haut, il s’agit avant tout d’un langage et comme tout langage il est simplement possible de l’écrire. Je vous redirige vers la documentation de Mozilla si vous souhaitez l’apprendre.

La manière la plus courante pour le moment reste l’utilisation de l’outil Emscripten. Il s’agit d’une chaine de compilation basée sur LLVM (Low Level Virtual Machine). Cette dernière est une infrastructure de compilateurs permettant de créer une machine virtuelle, un générateur de code et des optimiseurs de compilation. On notera que la LLVM propose entre autres des compilateurs à la volée (le fameux Just-In-Time). Emscripten va donc compiler le code source C/C++ en WebAssembly (dans un module .wasm) et générer le code Javascript encapsulant le module. Enfin il est capable de créer de l’HTML pour le rendu visuel.

Emscripten a donc 3 rôles principaux :

  • ● Envoyer le code C/C++ à LLVM
  • ● Compiler ce code en WebAssembly binaire (là nous obtenons notre .wasm)
  • ● Générer du HTML avec le code Javascript appelant le module WebAssembly.
  •  

En quelque sorte à ce moment là, le Javascript sert d’intermédiaire car le WebAssembly ne sait pas appeler l’API Web. Il a donc besoin de cet intermédiaire pour intéragir avec le navigateur.

Je vous fais le schéma :

 

Enfin il y a la manière toute simple d’utiliser un outil en ligne qui compilera comme un grand votre code en WebAssembly textuel et/ou binaire. Il ne reste plus qu’à utiliser le fichier .wasm téléchargé à partir du résultat. Vous pouvez puncher ce lien si vous souhaitez un exemple.

Cas d’utilisation

Nous pourrions nous dire: vu que la Streaming Compilation est aussi cool, pourquoi ne pas l’utiliser tout le temps ? Je répondrais que c’est une problématique déjà connue dans bien des technologies: ce n’est pas parce qu’on peut l’utiliser qu’il faut l’utiliser. Dans mon second article sur les choix techniques, je disais que chaque technologie correspond à un besoin avec ses spécificités, ses forces et ses faiblesses. Cela vaut également pour le WebAssembly.

Dans beaucoup de cas, il sera plus simple d’écrire directement du JS au lieu de devoir par exemple embarquer des libs standards de C, de devoir gérer les allocations et fuites mémoire et de devoir redéfinir un protocole ou la classe Array alors que le Javascript gère tout cela nativement. C’est donc une solution viable pour gérer des soucis de performances ou répondre à un besoin que le C/C++ sait mieux gérer que le Javascript.

On peut donc lister non exhaustivement quelques cas privilégiés d’utilisation du WebAssembly:

  • ● Traitement d’image
  • ● Reconnaissance d’image
  • ● Deep learning
  • ● Jeux vidéos : Que ce soit de petits jeux occasionels, des jeux à gros budgets classés AAA ou des moteurs de jeux en ligne…
  • ● 3D de manière générale (Réalité augmentée, simulation, réalité virtuelle)
  • ● Applications collaboratives
  • ● Outils de développement informatique

 

Et il existe un cas qui je trouve particulièrement intéressant: La réutilisation de code critique lors d’une migration. De nos jours, les applications vieillissantes ont de plus en plus tendance à être migrées sur des clients web légers. Cependant, le code peut parfois être critique, certains algorithmes particulièrement complexes et difficiles à réécrire, ou demandant de la puissance. On peut également parler des codes sources non documentés dont le reverse engineering serait abominable. Bref cela pourrait clairement baisser significativement des coûts de migrations. Intéressant non ?

Les grands principes du langage

Le WebAssembly est conçu pour répondre à certains principes.

Pour commencer il doit être un langage rapide et portable. Nous l’avons vu, il peut être éxecuté une vitesse proche de celle du CPU et s’inscrit sur plusieurs plateformes embarquant l’API WASM pour Javascript. Il faut tout de même noter que certains environnements d’éxecution n’offrant pas les caractéristiques requises selon la documentation officielle peuvent dans certains cas avoir à émuler un environnement d’éxecution afin que les modules puissent s’éxécuter comme si l’hôte ou le système d’exploitation les prenait correctement en charge. Dans ce genre de cas, cela peut entraîner de mauvaises performances.

Le WebAssembly doit également être un langage lisible et débugable. Il est en effet possible de l’écrire de manière textuelle ce qui permet de le débuguer normalement et d’offir une relecture aisée.

Ensuite, le langage doit être sécurisé. Dans ce domaine, le WebAssembly est un langage éxecuté en sandbox. Il s’agit d’un mécanisme de sécurité basé sur l’isolation de composants logiciels par rapport à leur hôte. De ce fait, il lui est impossible de faire directement des appels système ou de lire dans la mémoire du navigateur. Les règles d’autorisations des navigateurs lui sont également appliqué au même titre que le Javascript.

Pour finir le WebAssembly n’est pas un remplaçant de Javascript. Son but est de se coupler aux langages web pour les assister et répondre à des problématiques particulières. Du coup, on retrouve bien le fait qu’il n’y a pas d’interêt de vouloir l’utiliser partout.

Un peu de code

Je ne vous aurais pas écrit la conclusion sans vous montrer à quoi ça ressemble. C’est plutôt joli pour le moment, bien que ce ne soit pas aussi simple qu’un import javascript. Mais… c’est sur le feu cette histoire d’import, donc nous devrions bientôt pouvoir l’utiliser bien plus facilement. On peut également penser aux frameworks Javascript qui peut être un jour l’embarquerons nativement lorsque la technologie aura un peu maturé.

Voilà le bout de code qui nous servira d’exemple : un simple algo en C++ de calcul factoriel.

double fact(int i) {
  long long n = 1;
  for (;i > 0; i--) {
    n *= i;
  }
  return (double)n;
}

On le compile en .wasm et on peut utiliser le bytecode obtenu.

<html>
    <head>
        <meta charset="utf-8">
        <title>WASM test</title>
    </head>
    <body>
        <script>
            WebAssembly.instantiateStreaming(
                fetch("fact.wasm")
            )
            .then(obj => obj.instance.exports._Z4facti)
            .then(fact => {
                document.getElementById("fact").textContent = fact(5)
            });
        </script>
        <h1>La méthode C++ retourne :</h1>
        <p id="fact"></p>
        </body>
</html>

Donc l’API Javascript de WebAssembly nous donne la méthode instantiateStreaming(...) pour lancer la Streaming Compilation du fichier .wasm. Elle retourne une promesse sur laquelle on va aller chercher le nom de la fonction. Vu que j’ai utilisé le générateur dont je vous ai parlé plus haut, la mienne a un nom tout moche : _Z4facti. On va ensuite chercher l’élément DOM dans lequel je veux afficher le résultat et j’y attribut ma fonction avec son petit argument. Logiquement le résultat sera 120 (Ouais j’ai fais S au lycée t’as vu ?).

Bon donc là vous avez pris le code, compilé votre C++ pour obtenir votre bytecode, vous vous disiez waw c’est cool ça va marcher. Tenez vous bien on arrive sur la partie pas cool. Non seulement vous vous prenez l’erreur de CORS dans la tête, mais en plus même en réglant ce souci en lançant un live server via VSCode (le vénéré), vous avez cette erreur :

Uncaught (in promise) TypeError: Failed to execute 'compile' on 'WebAssembly': Incorrect response MIME type. Expected 'application/wasm'.

Sérieusement ? Yes. Bon bah faut configurer le serveur… Est-ce que un petit express vous tenterait ? Aller.

mkdir wasmTest
cd wasmTest
npm init
npm install express
touch index.js
mkdir public
cd public
touch index.html
cd ..

Voilà, ça c’est fait.

Dans index.js je vous propose de coller cela :

const express = require('express')
express.static.mime.types["wasm"] = "application/wasm";
var app = express();

app.use('/', express.static('public'));

app.listen(3000, function () {
  console.log('Example wasm app listening on port 3000 !')
})

…et dans votre répertoire /public vous allez placer le fichier .wasm et le code HTML donné plus haut dans le fichier index.html.

On exécute tout ça:

node index.js

 

Plus qu’à aller sur http://127.0.0.1:3000/ et vous trouverez votre HTML avec le résultat de la fonction.

Et voilà vous venez d’éxecuter du C++ sur un navigateur. Plutôt sympa, non ?

Conclusion

C’est fou comme cette technologie à l’air prometteuse. Elle peut répondre à énormément de problématiques que bien des entreprises peuvent rencontrer et donner une nouvelle dimension au développement web. Cependant, c’est une technologie très jeune qui a vraiment besoin de grandir, de faire ses preuves concrètement sur des projets complexes avant d’être considérée comme étant une révolution. Il y a eu des jeux vidéos créés avec le WebAssembly mais qu’en est-il des applications évoluant dans des systèmes d’informations aussi massifs que complexes ? Quelle est sa réelle compatibilité au sein d’un projet de grande envergure ?

Je souhaiterais terminer cet article avec une ouverture sur une réflexion. Si le WebAssembly prend beaucoup d’ampleur, est-ce qu’un développeur web devra également impérativement connaître les langages systèmes ? Est-ce qu’un développeur C/C++ devra également savoir faire du web ? Pour le moment, il me semble que ces deux mondes soient encore pas mal séparés formant au final deux métiers assez différents.

J’espère que cet article vous aura plu et je vous donne rendez-vous dans le prochain.

Happy coding et prenez soin de vous !

Bien à vous,

Pophip

Rémi Perreira

Consultant en développement