Le projet Da Vinci Machine, ou le support des langages dynamiques pour la JVM

Le but de cet article est de présenter de manière technique une des nouveautés du JDK7, le support des langages dynamiques pour la JVM.

Depuis la sortie de Java 6, le monde Java a beaucoup évolué. En effet, un grand nombre de langages, autre que le Java, ont commencé à être utilisés massivement sur la JVM. On peut citer par exemple Groovy, Ruby avec JRuby. Ces langages ne sont certes pas nouveaux sur la plate-forme Java, mais en 3 ans ils ont énormément évolués et sont désormais courants dans le monde de l’entreprise.

Depuis l'intégration du moteur de JavaScript Rhino dans le JDK 6, une mouvance de langages à typage dynamique est arrivée sur la JVM.

Du fait de l'inadaptation de la JVM, le besoin d'intégrer des notions de typages dynamiques s'est fait sentir. C'est ainsi qu'est né la JSR 292, améliorant le support des langages dynamiques.

Le projet Da Vinci Machine

Celui-ci, qui est projet OpenJDK, a été créé dans le but de transformer la JVM en une MLVM (Multi Language Virtual Machine). Ce projet regroupe plusieurs sous-projets dont le but est de réduire la difficulté d'intégrer de nouveaux langages pour la JVM.

En complément de la JSR 292, on peut retrouver : - une implémentation des continuations, chères aux langages fonctionnels - l'injection d'interface et de nouvelles méthodes dans une classe, chère aux langages dynamiques tels que Ruby

La liste complète des sous-projets est disponible ici.

Revenons à la JSR 292

Jusqu'à présent, les personnes en charge de l'implémentation de langages typés dynamiquement sur la JVM (entre autre Jython pour le Python et JRuby) ont dû rivaliser d'ingéniosité pour réussir l'intégration sur cette machine virtuelle. En particulier, ils utilisent à outrance le type Object, car ils ne peuvent pas connaître à l'avance le type de chaque objet, ils encapsulent les types de base dans des classes, etc. En "contournant" les restrictions de Java par ces biais, ils perdent les avantages de la JVM et ses capacités à optimiser l'exécution du code, ce qui résulte en de moins bonnes performances. De plus, le bytecode est globalement non-typé, donc il y a toujours un moyen de contourner le typage statique de la JVM, sauf pour un point majeur : l'invocation de méthode. En effet, le bytecode n'impose que deux restrictions sur les types :

  • Les appels de méthodes qui sont typés statiquement, c'est-à-dire, que les types des paramètres et du résultat sont déterminés à la compilation
  • Les appels de méthodes doivent se faire sur des types Java et sur des méthodes Java

De plus, la JVM vérifie le bytecode pour être sûr que les règles précédemment citées sont suivies. Si ce n'est pas le cas on tombe dans un puits (sans fin) d'exceptions. Pour plus d'informations sur ce point, je vous recommande le très bon article de Charles Nutter sur le sujet.

Ainsi, ces limitations devrait disparaître avec l'ajout du support de la JSR 292. Celle-ci est déjà disponible depuis le JDK 6 update 16 et dans le JDK 7. Ainsi, les langages à typage dynamique devraient être plus rapides que sur la JVM actuelle, tant qu'ils ont pris en compte les ajouts apportés par cette JSR.

Ce qu'apporte la JSR 292

La JSR 292 apporte deux nouveautés :

  • invokedynamic, une instruction de bytecode, qui permet d'appeler une méthode qui n'est pas encore définie (pour les connaisseurs, un peu comme invokeinterface)) en deleguant la resolution du code appelé à du code Java
  • une nouvelle API qui permet de travailler avec cette nouvelle instruction, le nouveau package java.dyn

Le détail du fonctionnement d'invokedynamic n'est pas très utile pour le développeur, donc je ne l'expliquerais donc pas. Concentrons-nous plutôt sur cette nouvelle API. Celle-ci comporte 6 classes importantes pour l'appel de méthode dynamique :

  • InvokeDynamic, permet de générer le bytecode invokedynamic, celle-ci ne sert que de sucre syntaxique pour l'appel de méthode dynamique. On peut donc appeler n'importe quelle méthode grâce à elle.  Ainsi, théoriquement, elle contient toutes les méthodes possibles
  • MethodHandle, est la définition d'une méthode au runtime (un peu comme la classe java.lang.reflect.Method)
  • MethodHandles, utilitaires pour la classe MethodeHandle
  • MethodType, permet de créer une signature d'une méthode au runtime
  • CallSite, qui permet de lier un invokedynamic avec une méthode réelle
  • Linkage permet de faire le lien entre la classe CallSite et les appels de méthodes dynamiques

Voici un petit exemple d'appels dynamiques (tiré de l'article d'Ed Ort), qui montre comment générer et utiliser l'instruction invokedynamic :

import java.dyn.*;

public class Hello {

  // Méthode qui va servir d'exemple pour les appels dynamiques
  static void greeter(String x) {
    System.out.println("Hello, "+x);
  }

  public static void main(String... av) {
    // Appel "classique" de la méthode greeter
    greeter(av[0] + " (from a statically linked call site)");

    // On itère sur les arguments du programme
    for (String whom : av) {
      // Génère un invokevirtual MethodHandle.invoke(String)void
       greeterHandle.<void>invoke(whom);  // appel typé fortement 

      // Génère un invokedynamic MethodHandle.invoke(Object)Object
      InvokeDynamic.hail((Object)x);               // appel typé dynamiquement
    }
  }

  //Création d'une signature de méthode : void maMethode(String)
  static MethodeType methodType = MethodType.make(void.class, String.class)

  // On fait le lien entre la méthode réelle greeter et
  // notre signature de méthode methodType
  static MethodHandle greeterHandle = MethodHandles.lookup().findStatic(Hello.class,
      "greeter", methodType);

  // Bootstrap du linkage
  static { Linkage.registerBootstrapMethod("bootstrapDynamic"); }

  // Méthode qui va effectuer la recherche et l'appel d'une méthode dynamique
  //quand une instruction invokedynamic est trouvée
  private static CallSite bootstrapDynamic(Class caller, String name, MethodType type) {
    // Adaptation des types de type au handle greeterHandle
    MethodHandle target = MethodHandles.convertArguments(greeterHandle, type);

    //Fait le lien entre l'appelant (caller), le nom de la méthode (name) et sa signature (type)
    CallSite site = new CallSite(caller, name, type);

    // On donne au CallSite la méthode réelle à appeler
    site.setTarget(target);
    return site;
  }
}

Conclusion

Le support de la JSR 292 est loin d'être terminé. En particulier, l'API n'est pas encore stable. Mais l'implémentation de cette JSR semble faire partie des nouveauté qu'apportera à coup sûr le JDK7.

Néanmoins, au moins deux projet supporte déjà cette JSR : Jython (depuis avril 2009) et JRuby (depuis septembre 2008). De plus, Charles Nutter, un des plus gros contributeur JRuby est aussi très actif dans la communauté du projet Da Vinci Machine.

Il semble donc que les communautés des langages dynamiques pour la JVM attendent avec impatience cette nouveauté, qui devrait leur apporter un gain important en terme de performance et de clarté de code. À suivre donc ...

Pour les geeks uniquement

Pour aller plus loin, vous pouvez trouver des builds du JDK 7 pour Windows, Linux et Solaris sur le site du JDK 7. Pour les possesseurs de Mac OS, la route est longue, très longue pour avoir un binaire, je vous fournis donc un petit how-to pour simplifier et compiler les choses :

  • Installer XCode
  • Installer mercurial
  • Récupérer le plugin forest pour mercurial :

hg clone http://bitbucket.org/pmezard/hgforest-crew/

  • Activer ce plug-in ainsi, que le plug-in mq dans son fichier ~/.hgrc

[extensions]

hgext.forest=/path/to/forest.py

mq=

  • Télécharger la branche BSD des sources du JDK (les options f(clone|update) sont fournies par le plug-in forest) :

hg fclone http://hg.openjdk.java.net/bsd-port/bsd-port sources

  • Installer SoyLatte, qui va permettre de bootstraper la compilation du compilateur javac. Le JDK 1.6 fourni par Apple avec Mac OS X n'est en effet pas compatible.
  • Récupérer la partie [non open source du JDK 7]( * http://landonf.bikemonkey.org/static/soylatte/jdk-7-icedtea-plugs-1.6.tar.gz)
  • Récupérer le script davinci-build.sh, qui va permettre de compiler "facilement" le JDK, le copier dans le répertoire sources/
  • Configurer le script
  • Le lancer directement depuis le répertoire sources/

cd sources && sh davinci-build.sh

  • Prier
  • Si vous avez correctement prié, vous avez un JDK 7 fonctionnel pour Mac OS X
  • Vous pouvez le tester (et notamment le invokedynamic)