|
|||||
Compilation 2003/2004: Projet
|
|||||
français
|
Partie 5
Date officielle pour le rendu: 06.02.2004 16h, par e-mail (compilation@lampsun1.epfl.ch) Le but de cette partie est de produire le code machine correspondant au programme analysé, en fonction des règles sémantiques du langage. Toutes les étapes précédentes avaient pour seul but de s'assurer que le programme était correct par rapport aux règles du langage, et de transformer le programme textuel en une forme facilement manipulable: l'arbre abstrait. Une fois ce travail préparatoire effectué, le compilateur peut commencer avec la production de code cible. La production de code nous occupera jusqu'à la fin du semestre. Nous séparerons cette partie en plusieurs étapes, que nous vous conseillons d'effectuer dans l'ordre donné, mais vous êtes bien entendu libres de vous organiser comme bon vous semble. L'idée générale de la production de code est relativement simple: il s'agit de parcourir l'arbre abstrait et de produire, pour chaque nœud, le code qui lui correspond. La production de code s'organise donc autour des visiteurs. L'implémentation des différents cas du visiteur peut être faite par étapes. Celles-ci sont décrites ci-dessous. La description de chaque étape commence par l'énumération des productions de la grammaire abstraite qui doivent être implémentées au cours de l'étape. Étape 1T = BasicType U | ListType T | FunType { T } T Les déclartions de types ne produisent pas de code et les méthodes
Étape 2E = Binop O E E | UnitLit | IntLit int | ... O = Add | Sub | Mul | Div | Mod | Eq | Ne | Lt | Le | Gt | Ge | ... Le but de la deuxième étape est de produire du code pour les
expressions arithmétiques. À la fin de cette étape, le compilateur
devrait être capable de compiler des programmes constitués d'une
simple expression arithmétique, comme Au cours de cette étape, on produit également le code pour la
valeur Étape 3E = If E E E | ... Le but de la troisième étape est d'ajouter la gestion des expressions conditionnelles au compilateur. Étape 4E = Binop O E E | Listop L E | NilLit | ... O = Cons | ... L = Head | Tail | IsEmpty Le but de la quatrième étape est d'ajouter la gestion des listes au
compilateur. La représentation que nous allons utiliser pour les listes est
basée sur des paires dont le premier élément contient la tête de la liste, et
le second sa queue. La queue peut être la liste vide En mémoire, on représente les paires par une zone contenant les
deux éléments de la paire côte à côte. On manipule ces zones au moyen
de pointeurs qui pointent vers leur début. L'opérateur Il faut noter que toutes les données que nous utilisons dans le
langage (les entiers, les pointeurs de fonction et les
[pointeurs vers les] listes) peuvent être représentés en mémoire par
un mot. Cela implique que toutes les paires ont la même
taille en mémoire, et que les opérateurs La figure ci-dessous montre comment la liste
Pour allouer la mémoire, vous devez utiliser l'appel système
Étape 5F = Formal ident T S = VarDef F E | ... E = Assign ident E | Ident ident | B | ... B = Body { S } E Le but de la cinquième étape est d'ajouter la gestion des blocs,
des variables locales et des affectations. Pour cela, il vous faut
gèrer la pile. Comme vous l'avez vu au cours, chaque appel de fonction
a le droit d'utiliser un bloc de mémoire, appelé bloc d'activation,
alloué sur la pile. L'addresse du début de ce bloc ne change pas au
cours d'un appel donné; il est désigné par Lors de la génération de code, vous devez calculer pour chaque
instruction quelle est la taille du bloc d'activation. Pour cela, la
classe Le nœud Pour générer le code du nœud La génération de code des nœuds Étape 6S = While E E | Exec E | ... Le but de la sixième étape est d'ajouter la gestion des boucles
Étape 7P = Program { D } B D = FunDef ident { F } T E E = Apply E { E } | ... Le but de la septième et dernière étape est d'ajouter la gestion des fonctions au compilateur. La gestion des fonctions inclut les fonctions prédéfinies
Pour cette étape, nous vous fournissons les fichiers suivants :
Pour tester les programmes produits par votre compilateur, nous
mettons à votre disposition un interpréteur et un débogueur graphique
simple. Ces outils prennent un certain nombre d'options, dont vous
pouvez obtenir la liste en les lançant avec l'option
L'interpréteur se lance en tapant
L'architecture utilisée a brièvement été présentée au cours. Les éléments importants sont rappelés ici. Le processeur comporte 32 registres, numérotés de 0 à 31. Quelques registres ont un usage particulier :
caseProgram de la classe Gen .
Toutes les instructions sont codées au moyen de 32 bits. Certaines parties de ces 32 bits peuvent contenir des constantes, dont il est important de connaître la taille au moment de la production de code. Sur la feuille distribuée au cours et décrivant le jeu d'instruction, ainsi que dans la documentation de l'interface RISC, ces constantes sont identifiées par un nom. La table ci-dessous donne la taille de ces constantes.
|