WebAssembly : le JavaScript le plus rapide ?

Il existe de plus en plus d’applications qui s'exécutent depuis un navigateur plutôt que de devoir être installées sur un disque dur. Outre les logiciels bureautiques classiques tels qu'Microsoft 365 ou Google Docs, dotés de nouvelles fonctions de plus en plus nombreuses, les jeux sur navigateur gagnent aujourd’hui en complexité et nécessitent des ressources toujours plus importantes. Ces applications Web étaient jusqu’ici créées en JavaScript. Aujourd’hui, les développeurs se tournent massivement vers WebAssembly, une nouvelle approche aux résultats étonnants.

WebAssembly, qu’est-ce que c’est ?

WebAssembly est un nouveau type de code exécutable depuis un navigateur. Jusqu'à présent, seul JavaScript permettait de créer des applications de ce genre. Son fonctionnement relativement lent atteignait ses limites dans certaines situations. C'est ce qui a poussé le World Wide Web Consortium (W3C) à promouvoir un nouveau type de code appelé WebAssembly (Wasm en abrégé). Cependant, pour pouvoir fonctionner correctement, Wasm doit être exécuté sur un navigateur capable de gérer ce langage. C'est pourquoi Mozilla (Firefox), Microsoft (Edge), Apple (Safari) et Google (Chrome) ont été sollicités pour participer à son développement. Dans toutes les versions actuelles des navigateurs les plus courants, les applications peuvent être exécutées en WebAssembly.

Conseil

Pour découvrir par vous-même la puissance de WebAssembly, nous vous conseillons de jouer à Funky Karts. Ce jeu, qui est au départ une application mobile, a été converti en WebAssembly pour pouvoir être exécuté dans un navigateur. Dans le cadre de ce projet, le développeur du jeu a tenu un blog intéressant sur lequel il a décrit les différentes étapes de la conversion.

Dans le principe, WebAssembly est une forme de bytecode pour navigateur. Le bytecode peut être considéré comme une étape intermédiaire entre le code machine, qui n’est compréhensible que par l'ordinateur, et un langage de programmation typique, lisible pour l'homme mais qui nécessite d’être compilé au préalable. C’est de là que WebAssembly tire sa relative rapidité : le travail de conversion du code par l'ordinateur est fortement minimisé. Mais il est généralement assez malaisé d’écrire en bytecode. L'avantage de Wasm, c’est qu’il évite de devoir travailler soi-même dans ce langage de programmation. Dans la pratique, on peut écrire une application Web en C ou C++, par exemple.

Le code source est ensuite converti grâce à l'application Emscripten. Cet outil existait avant l'arrivée de WebAssembly : sa raison d’être était de convertir le C/C++ en JavaScript (ou ams.js). Mais le code peut également être réécrit en Wasm. Il est précompilé, ce qui évite d'avoir à le compiler ou l’interpréter au moment de l'exécution de l'application. Lorsque celle-ci est ouverte dans le navigateur, une petite machine virtuelle est automatiquement lancée. Techniquement, c’est dans cette machine virtuelle que l'application fonctionne.

Pour afficher cette vidéo, des cookies de tiers sont nécessaires. Vous pouvez consulter et modifier vos paramètres de cookies ici.

Les avantages de WebAssembly

À l'heure actuelle, WebAssembly ne connaît qu’un seul véritable frein : sa diffusion reste relativement lente. Les développeurs Web sont habitués à travailler sur JavaScript, un langage dont la disparition n’est certainement pas programmée à l’heure actuelle. La direction du projet attache par conséquent une grande importance à la nécessité de promouvoir Wasm comme une option complémentaire au JavaScript. Toutefois, grâce au soutien apporté par les principaux fabricants de navigateurs et par le W3C, l’utilisation de Wasm se répand de plus en plus vite. Le fait que les visiteurs du site Internet porteur de l'application ne doivent prendre aucune mesure eux-mêmes n’y est sans doute pas étranger : les applications Web développées en WebAssembly se chargent aussi facilement que le code en JavaScript, et plus rapidement.

De plus, Wasm permet désormais aux nombreux développeurs qui écrivent en C, C++ exemple Rust de programmer directement pour Internet. Ces langages de programmation ont l’avantage d’apporter parfois d'autres options de conception d’une application : quand on ne parvient pas à trouver les bibliothèques ou les frameworks nécessaires à la création d’un programme en JavaScript, cela permet de bénéficier d’un arsenal bien plus vaste pour atteindre son objectif. Tout développeur peut donc y trouver des raisons de s’intéresser de plus près à WebAssembly :

  • Un standard Web ouvert du W3C
  • Hautes performances pour une petite taille de fichier
  • Par conséquent, il convient aussi parfaitement pour la navigation mobile
  • La réalité virtuelle devient aussi théoriquement accessible depuis un navigateur
  • Il n’est pas nécessaire d'apprendre un nouveau langage de programmation
  • Au contraire, les langages C, C++ ou Rust peuvent désormais être utilisés pour la programmation d'applications Web
  • Le projet est soutenu par tous les principaux fabricants de navigateurs
  • Aucune restriction n’est imposée à l'utilisateur

WebAssembly en pratique

WebAssemby n’est pas conçu pour faire réellement de la programmation. L’avantage principal de cette technique repose sur le fait qu’elle fait appel à un langage de programmation à la fois de haut niveau et très répandu, comme C, et que ce code est ensuite transféré sur le puissant format Wasm. Il peut toutefois être très utile d’examiner le code déjà compilé et de creuser la fonction WebAssembly.

Le code source est disponible en deux versions : WebAssembly Text Format (WAT) et WebAssembly Binary Format (Wasm). Ce dernier format correspond au code réel exécuté par l’ordinateur. Mais dans la mesure où il est exclusivement constitué de code binaire, il est quasiment impropre à l’analyse humaine. C’est la raison pour laquelle il existe le format intermédiaire WAT, qui utilise des expressions lisibles et peut ainsi être déchiffré par les programmeurs. En revanche, il n’offre pas le confort de travail habituel des langages de programmations bien établis.

À titre d’exemple, nous utilisons un code source très simple en C :

#define WASM_EXPORT __attribute__((visibility("default")))
WASM_EXPORT
int main() {
    return 1;
}

Le même code en format WAT est significativement plus long :

(module
    (type $t0 (func))
    (type $t1 (func (result i32)))
    (func $__wasm_call_ctors (type $t0))
    (func $main (export "main") (type $t1) (result i32)
        i32.const 1)
    (table $T0 1 1 anyfunc)
    (memory $memory (export "memory") 2)
    (global $g0 (mut i32) (i32.const 66560))
    (global $__heap_base (export "__heap_base") i32 (i32.const 66560))
    (global $__data_end (export "__data_end") i32 (i32.const 1024)))

La lisibilité est très limitée, mais l’on distingue tout de même les éléments suivants : dans WebAssembly, tout le contenu est divisé en modules, eux-mêmes divisés en fonctions, spécifiées par des paramètres. Au total on peut identifier 5 éléments distincts :

  • Module : unité supérieure dans WebAssembly
  • Function : regroupement au sein d’un module
  • Memory : tableau avec des octets
  • Global : valeur pouvant être utilisée dans plusieurs modules
  • Table : mémoire des références

Cependant, lorsque le code est traduit sous forme binaire, on ne peut rien distinguer de tout ceci :

0000000: 0061 736d                                 ; WASM_BINARY_MAGIC
0000004: 0100 0000                                 ; WASM_BINARY_VERSION
; section "Type" (1)
0000008: 01                                        ; section code
0000009: 00                                        ; section size (guess)
000000a: 02                                        ; num types
; type 0
000000b: 60                                        ; func
000000c: 00                                        ; num params
000000d: 00                                        ; num results
; type 1
000000e: 60                                        ; func
000000f: 00                                        ; num params
0000010: 01                                        ; num results
0000011: 7f                                        ; i32
0000009: 08                                        ; FIXUP section size
; section "Function" (3)
0000012: 03                                        ; section code
0000013: 00                                        ; section size (guess)
0000014: 02                                        ; num functions
0000015: 00                                        ; function 0 signature index
0000016: 01                                        ; function 1 signature index
0000013: 03                                        ; FIXUP section size
; section "Table" (4)
0000017: 04                                        ; section code
0000018: 00                                        ; section size (guess)
0000019: 01                                        ; num tables
; table 0
000001a: 70                                        ; funcref
000001b: 01                                        ; limits: flags
000001c: 01                                        ; limits: initial
000001d: 01                                        ; limits: max
0000018: 05                                        ; FIXUP section size
; section "Memory" (5)
000001e: 05                                        ; section code
000001f: 00                                        ; section size (guess)
0000020: 01                                        ; num memories
; memory 0
0000021: 00                                        ; limits: flags
0000022: 02                                        ; limits: initial
000001f: 03                                        ; FIXUP section size
; section "Global" (6)
0000023: 06                                        ; section code
0000024: 00                                        ; section size (guess)
0000025: 03                                        ; num globals
0000026: 7f                                        ; i32
0000027: 01                                        ; global mutability
0000028: 41                                        ; i32.const
0000029: 8088 04                                   ; i32 literal
000002c: 0b                                        ; end
000002d: 7f                                        ; i32
000002e: 00                                        ; global mutability
000002f: 41                                        ; i32.const
0000030: 8088 04                                   ; i32 literal
0000033: 0b                                        ; end
0000034: 7f                                        ; i32
0000035: 00                                        ; global mutability
0000036: 41                                        ; i32.const
0000037: 8008                                      ; i32 literal
0000039: 0b                                        ; end
0000024: 15                                        ; FIXUP section size
; section "Export" (7)
000003a: 07                                        ; section code
000003b: 00                                        ; section size (guess)
000003c: 04                                        ; num exports
000003d: 04                                        ; string length
000003e: 6d61 696e                                main  ; export name
0000042: 00                                        ; export kind
0000043: 01                                        ; export func index
0000044: 06                                        ; string length
0000045: 6d65 6d6f 7279                           memory  ; export name
000004b: 02                                        ; export kind
000004c: 00                                        ; export memory index
000004d: 0b                                        ; string length
000004e: 5f5f 6865 6170 5f62 6173 65              __heap_base  ; export name
0000059: 03                                        ; export kind
000005a: 01                                        ; export global index
000005b: 0a                                        ; string length
000005c: 5f5f 6461 7461 5f65 6e64                 __data_end  ; export name
0000066: 03                                        ; export kind
0000067: 02                                        ; export global index
000003b: 2c                                        ; FIXUP section size
; section "Code" (10)
0000068: 0a                                        ; section code
0000069: 00                                        ; section size (guess)
000006a: 02                                        ; num functions
; function body 0
000006b: 00                                        ; func body size (guess)
000006c: 00                                        ; local decl count
000006d: 0b                                        ; end
000006b: 02                                        ; FIXUP func body size
; function body 1
000006e: 00                                        ; func body size (guess)
000006f: 00                                        ; local decl count
0000070: 41                                        ; i32.const
0000071: 01                                        ; i32 literal
0000072: 0b                                        ; end
000006e: 04                                        ; FIXUP func body size
0000069: 09                                        ; FIXUP section size
; section "name"
0000073: 00                                        ; section code
0000074: 00                                        ; section size (guess)
0000075: 04                                        ; string length
0000076: 6e61 6d65                                name  ; custom section name
000007a: 01                                        ; function name type
000007b: 00                                        ; subsection size (guess)
000007c: 02                                        ; num functions
000007d: 00                                        ; function index
000007e: 11                                        ; string length
000007f: 5f5f 7761 736d 5f63 616c 6c5f 6374 6f72  __wasm_call_ctor
000008f: 73                                       s  ; func name 0
0000090: 01                                        ; function index
0000091: 04                                        ; string length
0000092: 6d61 696e                                main  ; func name 1
000007b: 1a                                        ; FIXUP subsection size
0000096: 02                                        ; local name type
0000097: 00                                        ; subsection size (guess)
0000098: 02                                        ; num functions
0000099: 00                                        ; function index
000009a: 00                                        ; num locals
000009b: 01                                        ; function index
000009c: 00                                        ; num locals
0000097: 05                                        ; FIXUP subsection size
0000074: 28                                        ; FIXUP section size
Conseil

Si vous souhaitez faire vos premiers pas en WebAssembly, vous pouvez vous tourner vers le Studio WebAssembly. Vous y trouverez un environnement de développement en ligne pour Wasm.