Asm.js, la compatibilité universelle des applications

Ce sous-ensemble de JavaScript défini par Mozilla donne aux applications et jeux presque la vitesse du code natif.

On pensait depuis longtemps à l'intérêt d'une machine virtuelle dans le navigateur permettant de compiler des langages différents et de les faire fonctionner sur tous les systèmes d'exploitation. Cette idée à pris forme grâce aux développeurs de Mozilla, et Asm.js est implémenté dans SpiderMonkey en 2013.

Capture de la vidéo Unreal Engine 3 avec Asm.js

Vidéo de Unreal Engine 3 avec Asm.js

Il ne s'agit pas d'un bytecode réel mais d'un sous-ensemble de JavaScript qui est compilé et optimisé pour la vitesse. Le code Asm.js est compilé AOT (une fois pour toutes - jusqu'à ce qu'on change le source), alors que le code JavaScript est JIT, compilé à la volée.

On peut compiler différents langages statiquement typés en bitcode LLVM, puis utiliser Emscriptem pour le convertir en Asm.js. Le code originel étant déjà optimisé, cela produit du JavaScript plus rapide et Asm.js lui apporte en outre la vitesse qui deux fois plus lente que le binaire mais dix fois plus rapide que JavaScript.
Une application entière peut être convertie en Asm.js ou seulement une bibliothèque venant d'un autre langage pour être utilisée par un programme JavaScript.

Avec le réalisateur de jeu Epic, Mozilla a implémenté le moteur 3D Unreal Engine 3 sous Asm.js (vidéo). Cela à incité les développeurs de V8 à optimiser le compilateur JavaScript de Chrome et Node.js pour Asm.js. Opera également à une optimisation pour le code Asm.js.
Microsoft de son coté a annoncé l'implémentation d'Asm.js dans Windows 10 et pour ce faire, travaille en collaboration avec Mozilla.

Le code Asm.js depuis les dernières optimisations en décembre 2013 met 1.5 fois le temps du code natif pour exécuter le même programme.

Asm.js est implémenté dans le nouveau navigateur Edge de Microsoft, avec l'aide de Mozilla. Cela a été commenté dans plusieurs billet de la firme, dont l'un titré: Bringing Asm.js to Chackra in Microsoft Edge.

Caractéristiques

Diagramme de la compilation en Asm.js
De C au bitcode LLVM à Asm.js

Asm.js est compatible avec JavaScript, et n'apporte aucune fonction supplémentaire mais pour être validé et compilé, ne doit contenir que le sous-ensemble défini par la spécification.

On ne peut tirer avantage d'Asm.js que si le langage compilé à des types statiques car c'est sur ce point qu'Asm.js trouve sa vitesse. Un langage dynamique devrait plutôt être compilé en JavaScript ordinaire pour fonctionner dans le navigateur.

Asm.js vs. Native Client vs. WebAssembly

Quelle solution doit-on préférer, Asm.js, ou NaCl, autrement dit des programmes compilés en bitcode pour LLVM, fonctionnant eux-aussi dans le navigateur?

Un programme sous NaCl même s'il fonctionne dans le navigateur doit être compilé pour chaque système d'exploitation. Donc le créateur doit fournir de multiples versions ce qui n'est pas le cas pour Asm.js qui fonctionne à l'identique sous tout système.
Sachant que Mozilla ne veut pas implémenter NaCl, cela l'exclut de Firefox, et apparemment d'Internet Explorer aussi.

Asm.js fonctionne d'office puisqu'un compilateur JS existe déjà dans chaque navigateur. On sait l'optimiser et il ne faut que quelques modification mineures pour qu'il fonctionne sur V8 ou autre JIT (Just-In-Time) ou AOT (Ahead-Of-Time).

L'annonce de WebAssembly en juin 2015, rend NaCl obsolète, d'autant qu'il sera supporté par tous les navigateurs alors que NaCl ne l'est que par Chrome. Mais ce n'est pas le cas d'Asm.js avec lequel il va coexister. En fait le code WebAssembly sera similaire au code Asm.js au départ, avant de diverger. Wasm pourra être converti en Asm.js pour fonctionner sur les anciens navigateurs. Il il pourra aussi être utilisé dans des modules JavaScript ou Asm.js.

Exemple de code

On définit la fonction strlen et on affiche les lettres d'une chaîne.

JavaScript:

function strlen(ptr)
{
  var curr = ptr;
  while(MEM8[curr] != 0)
    curr++;
  return (curr - ptr);
}
 
var ptr = "demo";
var len = strlen(ptr);
for(var i = 0; i < len; i++)
{
    document.write(ptr[i]); 
}

Asm.js:

function strlen(ptr) 
{ 
   ptr = ptr | 0;
   var curr = 0;
   curr = ptr;
   while (MEM8[curr]|0 != 0)
   {
       curr = (curr + 1) | 0;
   }
   return (curr - ptr) | 0;
}

var ptr = "demo";
var len = strlen(ptr) | 0;
for(var i = 0; i < len; i++)
{
    document.write(ptr[i]); 
}

Je ne garantis pas que document.write soit validé, il est là pour la démonstration.

Emscriptem avec l'option ASM_JS=1 produit du code Asm.js au lieu de JavaScript.

Documentation