Compilation 2004/2005 |
|||||
French only |
|||||
Partie 5 : Production de code
A présenter lors de la séance du 4 février 2005. Le but de cette partie est de produire le code machine correspondant au programme analysé. 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. Pour les autres parties du projet, on a toujours donné une spécification formelle de ce qu'il fallait implémenter sous forme d'une grammaire lexicale, d'une grammaire syntaxique et de règles de typage. Pour cette partie, on suppose qu'il y a un consensus sur la signification à l'exécution des différents concepts du langage Eins (fonctions, objets, opérateurs, etc). On se passera donc d'une description formelle (en cas de doute, utilisez le forum de discussion). 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 des visiteurs de génération de code peut être faite par étapes. Celles-ci sont décrites ci-dessous. La description de chaque étape commence par l'énumération des nœuds de la syntaxe abstraite qui doivent être implémentés au cours de l'étape. Étape 1 : les typesT = IntType | TypeIdent ident | FunType { T } T
Les déclarations de types ne produisent pas de code et les
méthodes Étape 2 : les expressions arithmétiquesE = IntLit int | Binop B E E | Unop U E | ... B = Add | Sub | Mul | Div | Mod | ... U = Neg | ...
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
Étape 3 : l'expression conditionnelleE = If E E E | ... Le but de la troisième étape est d'ajouter la gestion des expressions conditionnelles au compilateur. Étape 4 : les classes et les objetsP = Program { D } E D = ClassDef ident { F } | ... E = New ident { E } | Select E ident | NullLit | ...
Une classe définit un type. Ce type est utilisé pendant l'analyse
du programme mais n'a pas de contenu calculatoire. Autrement dit,
aucune instruction ne doit être générée pour une déclaration de
classe. Par contre, pour chaque symbole de champ de la classe, il
faut se rappeler de sa position dans la classe. On stocke donc
cette position dans le champ
Si
Concrètement, un objet est représenté dans la partie "tas" de la
mémoire par une zone contenant les valeurs de ses champs mises
côte à côte. La valeur d'un objet est l'adresse du début de
cette zone mémoire. La génération de code pour une expression
La génération de code pour le nœud de sélection de champ
(Select) utilise l'information stockée dans le champ
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] objets) peuvent être représentés en mémoire par un mot.
Pour allouer la mémoire, vous devez utiliser l'appel système
Étape 5 : les blocs et les variables localesF = Formal ident T S = VarDef F E | Assign ident E | ... E = Ident ident | Block { 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'adresse 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 6 : les bouclesS = While E { S } | Do E | ...
Le but de la sixième étape est d'ajouter la gestion des boucles
Étape 7 : les opérateurs logiques et de comparaisonsE = Unop U E | Binop B E E | ... U = Not | ... B = Or | Eq | Ne | Lt | Le | Gt | Ge | ...Remarque: pour comparer deux objets il faut comparer leur référence et non pas leur structure. Étape 8 : les fonctionsD = 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. Les arguments des fonctions doivent être évalués de gauche à droite avant d'être passés à la fonction.
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
Parfois l'exécution d'un programme Eins échoue à cause d'un dépassement de la mémoire. Il est possible de dire à l'interpréteur d'utiliser une taille plus grande pour la mémoire : risc-emu -mem=1000000 monfichier.risc
Pour utiliser les émulateurs RISC sur votre ordinateur
personnel, vous pouvez télécharger l'une des deux archives
suivantes:
risc-20050120.tar.gz ou
risc-20050120.zip. Aprés désarchivage, les
exécutables se trouvent dans le répertoire 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.
|
|