:: Enseignements :: Master :: M2 :: 2011-2012 :: Machine Virtuelle (et bazard autour ...) ::
![[LOGO]](http://igm.univ-mlv.fr/ens/resources/mlv.png) | 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.
-
Pourquoi est-on obliger d'utiliser de design pattern visitor pour effectuer un parcours
de l'AST ?
-
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.
-
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).
-
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.
-
Ecrire les méthodes du visiteur correspondant au if et if/else.
-
En regardant le code correspondant aux instructions break et continue dans l'interpreteur, expliquez à quoi
sert la classe ControlTransferError et ses classes internes.
-
Ecrire la méthode du visiteur correspondant au for.
-
Ecrire la méthode du visiteur correspondant aux expressions binaires.
Attention à bien respecter la sémantique du language foo.
-
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.
-
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.
-
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.
-
Modifier le code précédent pour stocker la constante "hello world" dans
une variable et afficher la variable.
-
Ajouter au code précédent, un code qui stocke la constante 42
dans une variable et afficher le contenu de celle-ci.
-
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).
-
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.
-
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é ?
-
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 ?
-
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.
-
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.
-
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.
-
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.
-
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
-
A quoi sert la classe IfBranches ?
Pourquoi y-a-t'il un appel récursif à visitExpr() dans visitExpr ?
-
On cherche maintenant à ajouter les opérations +, -, *, / et %,
implanter la méthode getNumericOpcode pour cela.
-
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;
-
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.
-
Ajouter l'implantation des opérateurs && et ||.
-
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;
}
-
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.
© Université de Marne-la-Vallée