Skip to main content

Dynamic Linking

Le Dynamic Linking n’est pour le moment supporté que par LLVM, ainsi ce chapitre est réalisé en langage C++ avec le compilateur Emscripten.

Qu'est-ce que le Linking

Avant d'exploiter le Dynamic Linking avec le WebAssembly, nous allons tout d'abord commencer par comprendre ce qu’est le Linking de librairie, ainsi que les avantages et inconvénients de ces méthodes.

Le Static Linking consiste à empaqueter les différentes dépendances dont votre application a besoin dans un binaire unique, qui n’aura ainsi besoin d’aucune dépendance préalablement installer sur un système pour fonctionner. Ce genre de binaire est souvent appelé un binaire portable étant donné qu’il ne nécessite aucun fichier externe afin de démarrer. En contrepartie, on se retrouve très généralement avec un binaire qui pèse plutôt lourd et qui doit être remplacé lorsqu’on souhaite le mettre à jour.

Contrairement au Static Linking le Dynamic Linking permet quant à lui d’externaliser les différentes dépendances de l’application et de les joindre à l’application lors de son exécution. Dans de gros projets, on peut retrouver des fichiers avec l'extension .DLL (Dynamic Link Library), ce sont justement les librairies que l’application va intégrer au lancement. L’avantage principal de cette approche est la possibilité de remplacer unitairement les dépendances de l’application lorsqu’elles sont mises à jour. Mais cela est couteux en termes de performances et de temps de chargement lors de l’utilisation de l’application.

Et pour finir le Dynamic Loading qui permet de la même manière que le Dynamic Linking de séparer en plusieurs binaires notre application, mais il va nous permettre tout simplement de les charger au besoin. C’est potentiellement la méthode qui nous intéressera le plus étant donné qu’il permet d’alléger la charge réseau sur notre navigateur. Et le plus gros avantage est que l’application est prête à l’emploi avant même que nous ayons chargé toutes les dépendances. Ce qui nous permettra ainsi de d’abord charger le module principal, puis de charger de manière asynchrone les modules secondaire.

Dans le cas d’utilisation du WebAssembly, étant donné que le binaire doit être téléchargé depuis internet, le poids du binaire devient ainsi une préoccupation auxquelles, le Dynamic Linking peut être une solution.

Le Linking en WebAssembly

Maintenant qu’on a vu ce qu’est le Linking de librairie, voyons comment il s’applique au WebAssembly. Nous allons nous baser sur une application afin de voir ensemble comment mettre en place les différentes méthodes.

L’application sur laquelle nous allons nous baser consiste en un Main qui recense les différentes opérations de calcul que peut effectuer un utilisateur ainsi que les différents modules qui comporte la logique de ces opérations.

Static Linking

Étant donné que ce n’est pas l’objectif de ce chapitre, nous allons simplement voir dans cette section comment est constituée notre application de référence et faire un rappel sur Emscripten.

Composition du projet

Tout d’abord, voyons de quoi est composé le projet. On remarque deux choses, d’abord qu’il y a des fichiers .cpp qui servent pour notre application en WebAssembly.

image.png

On retrouve également un fichier « package.json » qui correspond au fichier de configuration d’un projet Node, celui-ci va nous aider à lancer la compilation de notre application.

image.png

Si on regarde dans le package.json, on retrouve la commande de compilation de notre WebAssembly qui va permettre après une réadaptation de changer notre méthode de Linking. On trouve aussi la commande serve qui sert à lancer un serveur HTTP permettant de servir les différents fichiers, ce qui peut être nécessaire étant donné que certains navigateurs ne peuvent accéder à des fichiers statiques qu’au travers d’un serveur.

Dans le fichier index.html, on retrouve notre affichage qui se présente sous la forme de deux entrées de texte, une dropdown pour choisir l’opérateur, un bouton égal et une zone d’affichage pour notre résultat.

image.png

Le fichier index.html est composé d’un module JavaScript qui se charge d’importer le fichier généré par le compilateur et de mettre à disposition du front, une fois notre module charger, l’instance de notre module ainsi que la fonction permettant de faire appel à notre module.

image.png

La fonction calculate est très simple, elle récupère les entrées utilisateur depuis le DOM, fait appel à notre fonction de calcul écrite en WebAssembly et affiche la valeur de retour à l’utilisateur.

image.png

Rappel Emscripten

Faisons un bref rappel de comment on utilise Emscripten avec la commande que l’on utilise pour notre application.

em++ --bind -s EXPORT_ES6=1 -o calculator.out.js src/modules/subtraction.cpp ./src/modules/addition.cpp src/calculator.cpp

Tout d’abord, il faut voir l’outil em++ comme un équivalent de g++ mais qui compile exclusivement nos fichiers sources pour du WASM. Donc pour compiler un binaire cela se passe de la même manière, on spécifie les fichiers qui doivent être compilé et on spécifie le fichier de sortie.

  • -o FICHIER{.wasm, .js, .html} : ce qui différencie cet argument avec celui de g++ c’est qu’au lieu de spécifier un fichier object, on spécifie soit un fichier .wasm, .js ou .html. Ce qui génère tous les fichiers nécessaires (-o main.html génère le fichier html ainsi que .js et .wasm), ainsi dans notre cas, on spécifie qu’on souhaite générer les fichiers js et wasm.
  • --bind : permet de dire au compilateur qu’on a des fonctions que l’on aimerait rendre disponible pour une utilisation dans notre code JavaScript.
  • -s EXPORT_ES6 : permet de générer les fichiers .js en ES6.

Il ne faut pas oublier de spécifier quelles fonctions on veut exporter ainsi que le nom par lequel on pourra les appeler dans notre code JavaScript. D’abord, on importe la librairie d’emscripten <emscripten/bind.h> puis comme sur l’image qui suit on spécifie la fonction que l’on veut exposer.

image.png

Dynamic Linking

Afin de lier nos modules à notre application, on va devoir faire appel aux arguments fournis par Emscripten, Si tout se passe bien, on ne devrait avoir à faire aucune modification dans notre code pour transformer en module WASM nos fichiers source.

Avant de commencer, veillez à garder la commande de compilation originale, car on va devoir la retravailler pour créer notre module principal.

Pour ce faire, on va d’abord commencer par déclarer les modules secondaires en séparant en plusieurs commandes pour chaque module que l’on va créer et en ajoutant l’argument qui désigne notre binaire WASM comme étant un module secondaire.

em++ -s SIDE_MODULE=1 ./src/modules/subtraction.cpp -o subtraction.out.wasm

em++ -s SIDE_MODULE=1 ./src/modules/addition.cpp -o addition.out.wasm

une fois les modules compilés avec les commandes précédentes, on va reprendre la commande originale et on va simplement déclarer que c’est devenu un module principale grâce à l’argument -s MAIN_MODULE=1 puis pour que le module sache quelle sont les librairies qu’il doit intégrer lors de l’initialisation, on doit définir les fichiers .wasm précédemment compiler dans notre commande.

On devrait se retrouver avec une commande tel que la suivante.

em++ --bind -s EXPORT_ES6=1 -s MAIN_MODULE=1 -o calculator.out.js src/calculator.cpp addition.wasm subtraction.wasm

Maintenant compilons le module principal et voyons ce qui se passe dans le navigateur.

image.png

On voit à présent que lors du chargement de la page, que tous nos fichiers WASM ont été téléchargés afin de lancer notre application.

Dynamic Loading

Nous allons désormais nous intéressez à la partie qui va nous permettre de réduire l’utilisation du réseau en téléchargeant nos modules au besoin.

Dans nos modules, nous allons déclarer les fonctions que l’on veut exporter avec l’instruction extern.

image.png

Contrairement au Dynamic Linking, on va devoir apporter des modifications à notre module principal. On va notamment faire usage de la fonction dlopen() cette fonction va nous permettre de remplacer nos instructions include et ainsi de chargé une fois appelé le module spécifié dans la fonction.

Étant donné que l’on doit retirer nos *include*, les signatures des fonctions doivent être définies dans notre module principal, autrement, on ne sait pas dans notre module principal comment nos fonctions doivent être appelé.

image.png

Dans le code suivant, nous faisons un appel à la fonction dlopen() et nous récupérons un pointeur sur un handle vers la librairie que nous venons de charger. Enfin, on récupère grâce à la fonction **dlsym()** l’adresse de notre fonction que l’on va cast avec la signature définie précédemment.

image.png

Une fois les modifications faites dans notre code, les commandes doivent être réadaptées pour le Dynamic Loading.

Dans nos commandes permettant de compiler les modules secondaires, il n’y a pas beaucoup de changements, seul l’argument -s EXPORT_ALL=1 est ajouté afin de permettre l’accès aux fonctions de ce module.

Pour ce qui est du module principal, on doit lui retirer les noms de nos modules qui permettaient d’indiquer, ils devaient être importés au lancement. Puis, nous allons ajouter à la place l’argument --preload-file qui prend soit un fichier ou bien un répertoire.

Il faut savoir que l’argument --preload-file permet d’empaqueter dans un fichier .data nos différents modules, ce qui les rend disponibles une fois le chargement du paquet terminé. Mais rien ne nous empêche d’implémenter notre propre moyen de chargement des modules en JavaScript.

Nous retrouvons donc la commande suivante, qui permet de compiler le module principal et de fournir tous les modules supplémentaires nécessaires pour le bon fonctionnement de l'application. En effet, si nous ne spécifions pas les modules à lier au lancement, la commande va les chercher et les charger au fur et à mesure des opérations. Cela nous permet d'avoir une application plus fluide et plus efficace.

em++ --bind -s EXPORT_ES6=1 -s MAIN_MODULE=1 -o calculator.out.js src/calculator.cpp --preload-file assets/modules

Ainsi que cette commande qui permet de compiler un de nos modules.

em++ -s SIDE_MODULE=1 -s EXPORT_ALL=1 ./src/modules/subtraction.cpp -o assets/modules/subtraction.out.so

Maintenant que notre projet est prêt, compilons le et voyons ce qui se passe :

image.png

On voit sur la capture d’écran que désormais les modules ne sont pas plus directement chargés et ont été remplacés par le fichier calculator.out.data

Ressources

wasm-calculator-dynamic-linking.rar

wasm-calculator-dynamic-loading.rar

wasm-calculator-static.rar

https://medium.com/@arora.aashish/webassembly-dynamic-linking-1644c9f40c8c

https://www.dynamsoft.com/codepool/webassembly-standalone-dynamic-linking-wasm.html