:: Enseignements :: Master :: M2 :: 2011-2012 :: Machine Virtuelle (et bazard autour ...) ::
[LOGO]

Lab 1 - Preparation


Le but de cette préparation au lab 1 est d'écrire successivement un afficheur, un interpreteur ainsi qu'un générateur de code pour le langage foo. Celui-ci est décrit dans les deux documents suivants

L'archive ZIP correspondant à la préparation du lab1 est disponible ici.

Mise à jour, si vous avez chargé l'archive ci-dessus avant le vendredi 20 janvier à 18h45, il y a une erreur dans la grammaire qui définie le if/else, il vous faut donc recharger le zip et mettre à jour le repertoire parser-ast de votre projet en copiant celui fourni dans le ZIP ci-dessus puis en lançant la commande ant dans le répertoire parser-ast ou recopier le fichier parser-ast.jar qui se trouve dans lib en prenant le version qui se trouve dans l'archive ZIP.

Par défaut, Eclipse n'est pas configuré pour utiliser le JDK7. Aller dans Window/Preference < Java/Installed JREs et ajouter un nouveau JRE nommé Java7 qui se trouve à l'adresse /usr/local/apps/java7. Puis dans Window/Preference < Java/Compiler, passer le compilateur en mode compatible 1.7.

Exercice 1 - Print them all

Le but de ce premier exercice est de se familiariser (re-familiariser devrais-je dire) avec le design pattern visitor et l'arbre de syntax abstraite (AST).
On souhaite juste afficher l'AST issue du parsing d'un script, donc pour chaque noeud de l'arbre afficher ses informations puis si celui-ci possède des fils, afficher récursivement ceux-ci.
La classe fr.umlv.foolang.printer.Printer contient le squelette de l'afficheur. La classe PrinterMain permet de lancer celui-ci sur un script passé en paramètre ou donné sur la ligne de commande.

  1. Pourquoi est-on obliger d'utiliser de design pattern visitor pour effectuer un parcours de l'AST ?
  2. Rappeler le principe de fonctionnement d'un visiteur et à quoi servent les méthodes dispatch de la classe Printer et accept de la classe fr.umlv.foolang.ast.Node.
  3. Compléter la classe fr.umlv.foolang.printer.Printer pour afficher les informations relative aux noeuds marqués TODO dans le code du visteur.
    Tester que votre code marche avec tout les exemples du répertoire samples.

Exercice 2 - Y-a-t'il un interprète dans la salle ?

On cherche maintenant à écrire un interpreter pour le language foo, pour cela on va créer un nouveau visteur fr.foo.lang.interpreter.SimpleInterpreter qui au lieu d'afficher des choses à l'écran va renvoyer les valeurs correspondant à 'évaluation des expressions du language.
Le langage foo comme le C permet d'avoir des variables ayant le même nom s'ils sont définies dans des scopes différents, un nouveau scope étant ouvert à chaque nouveau block. Voici un exemple expliquant comment marche la portée des variables.
      fun foo() {
        var a = 1
        {
          var a = "foo"
          print(a)    // affiche foo
        }
        {
          var a = true
          print(a)    // affiche true
        }
        print(a)    // affiche 1
      }
      
La classe VariableStorage correspond aux variables qui sont définie pour un scope donnée et délégue à son parent le fait de saavoir si une variable est dans le scope précédent. La classe Variable définie ce qu'est une variable pour l'interpreteur (un nom, un type et une valeur).

  1. Ecrire/compléter les méthodes du visiteur permettent de faire fonctionner l'exemple ci-dessus. La méthode print sera considérer pour l'instant comme étant la seule méthode existante.
  2. Ecrire les méthodes du visiteur correspondant au if et if/else.
  3. En regardant le code correspondant aux instructions break et continue dans l'interpreteur, expliquez à quoi sert la classe ControlTransferError et ses classes internes.
  4. Ecrire la méthode du visiteur correspondant au for.
  5. Ecrire la méthode du visiteur correspondant aux expressions binaires. Attention à bien respecter la sémantique du language foo.
  6. Completer la méthode du visiteur correspondant appel de fonction pour appeler non pas uniquement print mais aussi les fonctions du script lui-même. On considèrera qu'une fonction ne peut appeler que les fonctions déclarées avant elle-même.
  7. On veut pouvoir appeler les fonctions même si celle-ci sont déclarées à posteriori, pour cela vous allez utiliser une table de hachage pour pré-enregisterer toutes les fonctions existantes avant d'appeler l'interpreteur. Modifier votre conde en conséquence.

Exercice 3 - You say bytecode ?

Le but de cette exercice est de se familiariser avec la génération de bytecode en utilisant la bibliothèque ASM 4.
Cette bibliothèque permet de s'affranchir de plusieurs contraintes inhérent au bytecode tout en restant suffisamment proche de celui-ci pour générer ce que l'on veut (par exemple, du code pas valide en Java amsi valide aux yeux de la JVM).
Noter que vous pouvez écrire le code correspondant en Java puis utiliser javap pour afficher le bytecode correspondant. De plus, l'ASMifier permet de générer à partir d'un .class un code source Java qui va générer le .class passé en paramètre.
Petit conseil, n'abusez pas trop de l'ASMifier, le but de cette exercice est de comprendre précisément l'ensemble des appels que vous allez faire à travers l'API d'ASM, le but final étant de se préparer à écrire un générateur de code pour le langage foo.

  1. Ecrire un programme qui génère un fichier "Foo.class" qui lorsqu'il est exécuter par une machine virtuelle Java affiche "hello world".
    Pour cela, vous devez générer une classe ayant une méthode main ayant le code suivant
             getstatic java/lang/System out Ljava/io/printStream; 
             ldc "hello world"
             invokevirtual java/io/PrintStream println (Ljava/lang/String;)V
           
    La classe devra généré devra être au format correspondant à Java 7 (1.7), nous en auront besoin lors du lab2.
  2. Modifier le code précédent pour stocker la constante "hello world" dans une variable et afficher la variable.
  3. Ajouter au code précédent, un code qui stocke la constante 42 dans une variable et afficher le contenu de celle-ci.
  4. Modifier le code précédent, pour stocker les constante 42 et 7 dans deux variable, faite l'addition et afficher le résultat. Faire la même chose pour la soustraction, la multiplication, la division et le modulo (le reste de la dision entière).
  5. Ajouter un nouveau code qui effectue les mêmes opérations qu'à la question précédente mais avec des doubles au lieu d'entiers.
  6. Ecrire un code permettant de générer le code suivant
            var a = 3
            if (a < 10) {
              print("bingo")
            }
          
    Pourquoi l'opcode du test est-il inversé ?
  7. Faire de même avec le code suivant.
            var a = 3
            if (a < 10) {
              print("foo")
            } else {
              print("bar")
            }
          
    Que se passe-t'il si au lieu d'un print("foo") l'instruction est un return ?
  8. Ecrire un code permettant de générer le code suivant
            var a = 3;
            var foo = (a < 10);
          
    Comparer avec le code écrit à la question précédente.
  9. Ecrire un code permettant de générer le code suivant
            var a = 3;
            var and = (a < 10) && (a == 10);
            var or = (a < 10) || (a == 10);
          
    Noter comment est implanter le fait que le 'et' booleén et le 'ou' booléen sont paresseux.
  10. Ecrire un code permettant de générer le code suivant:
          for(var i = 0; i < 10; i = i + 1) {
            print(i)
          } 
          
    Notez que le code que vous avez écrit est très similaire à celui des questions précédentes.
  11. Dans un autre fichier, écrire un programme qui gènère la fonction de fibonacci (cf samples/fib.foo) et qui test dans un main l'appel à fib(7).

Exercice 4 - Là où ya pas de Gen, ya pas de plaisir

Nous allons écrire un générateur simple de bytecode permettant de transformer un script en bytecode afin de l'exécuter. Au lieu de générer un fichier comme dans l'exercice précédent, la génération du bytecode se fera en mémoire dans un tableau d'octets qui sera directement 'injecté' en tant que classe à la machine virtuelle.

Le language foo étant un language dynamique, les variables peuvent ne pas être typés, hors nous avons vu dans l'exercice précédent que le bytecode est typé (comme les codes assembleurs des CPUs depuis au moins le PDP-11). Ici, nous allons simplifier le problème en considérant que les deux seuls types permits sont int et boolean (boolean car en interne il est représenté par un int par la VM avec la convention 0=false, 1=true comme en Java). Donc le générateur ne génèrera pas exactement du bytecode pour le language foo mais pour le language foo pourvu que toutes les variables soient des ints.
      People can have the Model T in any color - so long as it's black.
      
                                                    - Henry Ford
     

En plus de faire la génération du code, le visiteur devra vérifier que le programme ne possède pas de code mort. Pour deux raisons, le code non atteignable trouble ASM car celui-ci ne peut pas calculer les strack frames comme il faut, de plus la semantique du language foo permet comme en C de ne pas finir le block d'une fonction qui est typé void par un return. Pour cela, il faut detecter si un return est déjà présent ou pas, ce qui n'est pas si simple à cause des tests et des boucles. Noter que comme ici, on se restreint aux types int et boolean, une fonction ne peut pas renvoyer void à part le block main.

  1. Ecrire les méthodes du visiteur permettant de générer le code de la déclaration, l'assignation des variables, l'utilisation de variable et de constante ainsi que le print
  2. A quoi sert la classe IfBranches ? Pourquoi y-a-t'il un appel récursif à visitExpr() dans visitExpr ?
  3. On cherche maintenant à ajouter les opérations +, -, *, / et %, implanter la méthode getNumericOpcode pour cela.
  4. Implanter les opérations de comparaisons ==, !=, <, lt;=, > and >= en utilisant la méthode visitJumpInsn de l'interface MethodVisitor. Vérifier que votre implantation marche aussi bien si l'expression est la condition d'un if ou pas;
  5. Implanter la méthode du visiteur correspondant à un if/else. Attention à gérer la liveness correctement dans le cas où la première branche est morte ou dans le cas ou les deux branches sont mortes.
  6. Ajouter l'implantation des opérateurs && et ||.
  7. Ecrire les méthode du visiteur corrrespondant à la boucle for. Vérifier que les codes suivants marchent ou lève l'exception en cas de code mort.
              for(var i = 0;;i = i + 1) {
                print(i)
              }
            
              for(var i = 0;;i = i + 1) { // i = i + 1 est un code mort
                break;
              }
            
  8. Modifier la méthode du visiteur pour permettre les appels de fonctions. On supposera qu'à part print toutes les autres méthodes retourne un entier. Comme pour l'interpreteur, le generateur devra être capable de générer le code d'un appel à une méthode même si la définition de celle-ci est situé plus bas dans le fichier pour cela le generateur va enregistrer la signature de toute les méthodes avant de générer le code.