CSC4536– Compilation : De l'algorithme à la porte logique

Portail informatique
La documentation commune aux différentes étapes du projet est regroupée dans la page Mémento du projet MiniJava
  • Récupérer l'archive Minijava.tar.gz.
  • Exécuter la commande Compil-new-link à la racine du projet (création du lien ./lib -> LIBCOMP/lib)
  • Intégrer l'arborescence comme un projet Java dans votre IDE favori.
  • Intégrer "build.xml" dans la "vue Ant" de l'IDE. NB: L'utilisation de make ou ant en ligne de commande reste fonctionnelle
  • Lire en détails, la section "1. Structuration des sources" de la documentation Mémento MiniJava et regarder les classes fournies.
  • Tester la génération et l'exécution du compilateur.
  • Les questions et la curiosité sont bienvenues.

Le compilateur de l'étape 0 est fonctionnel mais uniquement pour une syntaxe très simpliste ("Hello 42" == ./Exemples/Test101.java). Dès que l'on va étendre la syntaxe, le comportement du reste du compilateur est indéfini. Ajouter dans la classe main.Compiler, un appel à la méthode main.DEBUG.toBeContinued() entre la phase d'analyse syntaxique et les phases suivantes. Cette balise avancera ensuite avec les différentes étapes du projet.

Le travail est à réaliser de manière incrémentale en ajoutant au fur et à mesure les différentes règles de la grammaire et en validant avec le fichier "input.txt" à compléter. L'ensemble du compilateur sera réalisé dans un premier temps en ignorant les tableaux d'entiers (milestone 9). L'étape finale du projet ajoutera les tableaux d'entiers dans l'ensemble des phases du compilateur.
  • Lire la documentation du projet Mémento MiniJava, sections 2, 3 et 4.
  • Écrire les spécifications CUP et JFlex pour réaliser l'analyse lexicale et syntaxique du langage Minijava.
  • Construire l'Arbre de Syntaxe Abstraite en suivant les définitions du package syntax.ast
  • Tester et valider l'analyse syntaxique en vérifiant l'impression des AST, et le résultat du visiteur syntax.PrettyPrint. Adapter si besoin la classe main.DEBUG pour modifier les traces imprimées par le compilateur.
  • Utiliser les fichiers de test dans ./Exemples, en particulier : Test[1-3]0[1-6], TestPeano, TestAckermann.
Le langage minijava est un sous-ensemble du langage Java. Pour les tests, il peut donc être utile dans les différentes étapes du projet de comparer le comportement de votre compilateur Minijava avec celui de javac. La paire de spécifications :
N'oublier pas de lire le mémento du projet qui contient un ensemble d'informations utiles ou indispensables qui ne sont pas reprises dans cette page. ( Sections 4 à 7 pour cette étape ) Complément du mémento : la "Classe Main" est purement conventionnelle dans Minijava. Les seuls traitements indispensables de cette classe sont déjà intégrés dans les sources fournies. On ignorera donc son existence dans toutes les étapes qui suivent.
Cette étape suppose d'avoir une analyse lexicale et syntaxique opérationnelle pour le langage Minijava sans les tableaux d'entier. Compléter l'étape précédente en utilisant éventuellement le corrigé.

  • Écrire un visiteur semantic/TestIdent qui hérite de systax.ast.ASTVisitorDefault et qui imprime l'ensemble des identificateurs des variables déclarées. Inclure l'appel au visiteur dans semantic/Semantic pour tester.
  • Adapter le visiteur pour préciser pour chaque variable, si il s'agit d'un attribut de classe, d'une variable locale ou d'un paramètre formel de méthode
  • (bonus)Compléter le visiteur pour afficher les noms de classes et de méthodes déclarées.
Exemple de résultat sur ./Exemples/Test104.java :
= Test des Identificateurs : Test2 (klasse),a(field), b(field), Start(method), i(formal), j(formal), k(local), un(method), Test3 (klasse),zero(method),

Le visiteur syntax.PrettyPrint peut donner une idée de squelette, mais le travail demandé ici est beaucoup plus court (20-30 lignes).

Écrire un autre petit visiteur semantic.TestScope qui reconstruit uniquement la structure des accolades {} du fichier d'origine.
Exemple de résultat sur ./Exemples/Test104.java :
= TestPortées : Test2{Start{}un{}}Test3{zero{}}

Fusionner les 2 visiteurs précédents dans un seul visiteur semantic.TestFusion. On imprime ainsi l'ensemble des identificateurs à l'intérieur de leur portée respective.
Exemple de résultat sur ./Exemples/Test104.java :
= Test Ident ET portées : Test2{a(field), b(field), Start{i(formal), j(formal), k(local), }un{}}Test3{zero{}}
Bravo, vous avez construit l'arbre des portées (scope) des identificateurs et vous savez aussi trouver l'ensemble des déclarations. En somme, une table de symboles presque complète !
Outre la page de documentation du projet Minijava, Il convient de lire attentivement le contenu des classes du package semantic.symtab La construction de la Table des symboles pour le compilateur Minijava est réalisée par la classe semantic.BuildSymTab. Le squelette fourni est un visiteur qui ne fait rien, mais contient les helpers utiles pour réaliser le travail. En réalité, la table des symboles est initialisée dans la classe semantic.semanticTree et contient déjà la déclaration de la classe Object et sa méthode equals.

Écrire la classe semantic.BuildSymTab qui construit la table des symboles pour le langage Minijava :
  • Construire l'arbre des portées qui structure la table des symboles (cf. mémento Minijava, sources, et helpers)
  • Calculer l'attribut sémantique hérité "currentScope" qui associe à chaque nœud de l'AST, la portée où se trouve le nœud. Cet attribut réalise le lien vers les identificateurs visibles depuis ce nœud.
  • Stocker l'attribut sémantique "currentScope" (décoration de l'AST) dans la structure semantic.semanticTree pour pouvoir le réutiliser dans la suite de l'analyse sémantique et dans les phases suivantes du compilateur.
  • Stocker dans l'arbre des portées, l'ensemble des informations de déclaration d'identificateurs (classe, méthode, variable/attribut).
  • Gérer la "variable" this dont la déclaration est implicite dans l'AST :
    • Pour chaque méthode, ajouter dans la table des symboles une déclaration explicite de this comme premier paramètre de la méthode.
    • Le type de la variable "this" est le nom de la classe courante !
    • Calculer donc l'attribut sémantique hérité "currentKlass" qui associe à chaque nœud de l'AST, la classe englobante du nœud.
  • Réaliser enfin, la fonction sémantique "allready defined" qui détecte les erreurs de redéfinition d'identificateur au sein d'une même portée. Ce n'est fonctionnellement pas l'endroit pour faire cela, mais c'est très économique de le faire ici.
  • Valider le travail en vérifiant l'impression de la table des symboles (passe1).
    Le fichier "./Exemple/Test104.java" doit produire la table :
    = Table des Symboles (passe1) Scope Root class Object extends null class Test3 extends Test2 class Test2 extends Object Scope Object boolean equals(Object this, Object o) Scope equals_args Object this Object o Scope equals Scope Test2 int Start(Test2 this, int i, int j) int un(Test2 this) int a int b Scope Start_args Test2 this int i int j Scope Start int k Scope un_args Test2 this Scope un Scope Test3 int zero(Test3 this) Scope zero_args Test3 this Scope zero

La solution est déjà fournie dans le code ! Avant d'utiliser l'héritage des classes Java, il est urgent de valider l'absence de boucles dans l'héritage. Cette fonction sémantique est réalisée par la classe semantic.CheckInheritance (algorithmique très basique et peu optimisé!).
Dans la même classe, on va de plus reconstruire la racine de l'arbre des portées pour ajouter l'arbre d'héritage des classes (si c'est un arbre!) comme arbre d'héritage des visibilités des identificateurs. L'héritage OO deviendra alors transparent pour la recherche dans la table des symboles.

Parcourir rapidement le code et tester le fonctionnement en analysant l'impression de la table de symbole(passe 2) La gestion des erreurs est ici optimiste. c'est à dire que l'erreur a priori grave est signalée, mais l'on continuera la phase d'analyse sémantique simplement en ignorant l'héritage Java pour les classes qui n'ont pas "Object" comme ancêtre.

Écrire la fonction sémantique "Identifier allready defined". Déjà fait en même temps que la construction de la table des symboles !!! Comparer le comportement du compilateur Minijava avec celui de javac dans le cas d'une variable locale de méthode qui redéfinit un argument de la méthode.

Écrire un nouveau visiteur qui vérifie que tous les identificateurs utilisés dans l'AST, sont bien liés à une déclaration dans la table de symbole.
Ignorer pour le moment les identificateurs de méthode, qui ne peuvent être traités que après le contrôle de type : rechercher la méthode pour obj.method() nécessite de connaître le type de obj. NB: si l'on prend en compte la liaison dynamique de Java le test n'est plus après le contrôle de type mais éventuellement à l'exécution
On peut aussi ignorer l'identificateur de classe de la classe parent dont le contrôle est déjà fait dans semantic.CheckInheritance cf. version complète après le contrôle de type (dans le rendu final )

Intégrer dans le visiteur "Undefined" précédent la fonction sémantique qui signale l'ensemble des variables déclarées mais non utilisées. On notera que semanticTree.rootScope.getAllVariables() donne la collection de toutes les variables du programme. Il suffit donc de supprimer au fur et à mesure les variables utilisées pour avoir en fin de la visite les variables "unused". cf. version complète après le contrôle de type (dans le rendu final )

cf. memento minijava et code fourni ...
En cas de manque d'inspirations, les fichiers Exemples/Test4* sont adaptés pour les tests de la phase sémantique.
Le schéma de liaison entre les identificateurs et leurs déclarations réalisé avec la table des symboles consiste à rechercher la déclaration en partant de la portée courante et en remontant l'arbre des portées (cf. lookup() dans la classe Scope).
Ce schéma fonctionne pour les variables ou pour les méthodes de classe (static) mais ne fonctionne pas directement pour les méthodes d'instance. Pourquoi ?
Si l'on a x.get(); y.get(); dans une même portée, où chercher la déclaration de get() ?
En fait la question est incorrecte. Il y a potentiellement plusieurs déclarations différentes.
... solution ? ...
Pour une méthode d'instance, la recherche ne dépend pas de la portée courante mais dépend de la classe concrète du receveur (this). Pour x.get(), la déclaration de get() est à chercher à partir de la portée de la classe de x.

Dans le cas d'une liaison statique (cas pour Minijava), on considère le type déclaré de x qui est connu lors de l'analyse sémantique (contrôle de type). Dans le cas d'une liaison dynamique (Java), on considère le type réel de x (celui de l'instanciation avec new). La liaison dynamique est donc potentiellement réalisée à l'exécution ou dans une phase d'analyse sémantique dynamique (flot d'exécution et coutures d'arbres)

Écrire un visiteur qui calcule l'attribut Type de chaque expression dans l'AST Minijava. Le Type est un String avec des valeurs prédéfinies (main.TYPE) pour les types primitifs ou un nom de classe pour les types Objets. La classe semantic.TypeChecking est fournie comme squelette pour le contrôle de type.

Le Type est un attribut synthétisé qui est stocké dans la structure SemanticTree pour la suite de la compilation. L'Attribut Type est nécessaire pour le contrôle de type à suivre mais sera aussi indispensable pour l'allocation mémoire dans la phase de génération de code. Spoiler : en réalité, on ignorera l'attribut en allouant un mot de quatre octets pour toutes les variables quelque soit le type. La réalisation de cette question peut se faire en parallèle avec la question suivante. Le test et la validation sont en particulier communs aux 2 questions cf. question suivante

Intégrer dans le Visiteur précédent, le contrôle de type c'est-à-dire la vérification de l'ensemble des contraintes de typage du langage Minijava.

Dans Minijava, il n'y a pas de transtypages pour les types primitifs. Pour les types Object, on a le transtypage naturel lié à l'héritage des classes. Un objet possède le type de tous ces ancêtre. Ce mécanisme est implanté dans le squelette fourni et l'on a aucun besoin de mémoriser ces cas de transtypage pour les phases suivantes de la compilation.

On notera l'absence de surcharge des opérateurs, c'est-à-dire la possibilité qu'un opérateur change de sémantique en fonction du type des opérandes. Pour les méthodes, on ignore la liaison dynamique et les possibilités de surcharge, mais on permet la redéfinition (overriding).

Tester au fur et au mesure sur des exemples valides ou invalides. En cas de doute, on peut aussi comparer avec le comportement du compilateur javac sur les mêmes exemples.

Terminer et valider la question "undefined" de l'étape 2 en intégrant la recherche de méthodes. On ne traite que la liaison statique et l'on ignorera le polymorphisme des méthodes.
rendu final

Un petit correctif sur l'impression de la forme intermédiaire : Dans la classe intermediate.IR remplacer if (! (q instanceof QLabel)) par if (!((q instanceof QLabel)||(q instanceof QLabelMeth) ))

Pour simplifier les phases suivantes de la compilation, on réduit en peu le spectre du compilateur en supposant que :
  • Les méthodes deviennent maintenant globales, c'est à dire que les noms de méthodes sont uniques dans le fichier source. Dans la représentation intermédiaire comme dans le code final MIPS, une méthode correspond à un label avec la nom de la méthode. En cas de duplication, la détection se fera au niveau de l'assemblage avec MARS et un message "duplicated label"
  • Toutes les "Variables" de Minijava ou de la forme intermédiaire auront une implantation en mémoire identique avec un mot de 32bits. On simplifie ainsi le calcul des tailles, des alignements...

Ecrire un traducteur de l'AST vers la Représentation Intermédiaire "code à 3 adresses". La classe intermediate.Intermediate donne un canevas de départ avec les helpers utiles.

Il s'agit d'écrire un visiteur de l'AST avec:
  • Calcul (et stockage) d'un Attribut synthétisé "nodeVar", qui pour chaque nœud expression contient la variable (IRvar) utilisée par la forme intermédiaire pour stocker le résultat de l'expression. Cette variable est généralement une variable temporaire générée par le traducteur mais peut aussi être une constante ou une variable du programme d'origine.
  • Calcul d'un attribut hérité "currentMethod" qui contient le nom de la méthode courante. Cette information est utilisée uniquement pour associer une portée locale aux variables temporaires IRTempo et permettre une meilleure allocation mémoire.
  • Traduction des nœuds de l'AST sous forme de séquences d'instructions IRQuadruple. Les exemples de base pour les schémas de traduction sont illustrés dans les slides.
  • La traduction se fait à la volée au sens où le programme final est la concaténation des traductions de chaque nœud de l'AST en suivant l'ordre de la visite.

Réaliser le travail par étape en reprenant les milestones de la syntaxe Minijava et les exemples ./Exemples/Test10*.java.
Exemple de résultat : . .

La génération de l'assembleur est réalisée par la classe codegen.Codegen qui utilisera les classes des packages codegen et allocator :
  • La classe MIPS est une classe utilitaire pour l'impression dans un fichier des instructions MIPS. On peut l'adapter ou la compléter à la convenance de chacun. Correctif de bug dans la methode MIPS.store() lire
    inst("sw   " + r0 + ", " + offset + "(" + r1 + ")");
    au lieu de
    inst("lw   " + r0 + ", " + offset + "(" + r1 + ")");
  • La classe LinkRuntime réalise l'édition de lien qui se limite à la concaténation du "Runtime MIPS".
  • La classe IR2MIPS réalise la traduction de la forme intermédiaire vers MIPS. Pour le moment, elle ne gère que le programme "hello 42" (Test101).
  • Le package allocator construit, à partir de la table des symboles (AST +IR), l'allocation mémoire des variables, des classes, des frames ... (cf. question suivante)
Analyser rapidement le contenu et le fonctionnement de ces classes.

Analyser en détails le contenu de la classe allocator.Allocator. Expliquer en particulier :
  • Les différents types d'accès aux variables de la forme intermédiaire et la mise en œuvre à travers les classes Access*
  • La gestion des variables instances de classe : instanciation et accès aux champs.
  • La mise en œuvre de la convention d'appel et le calcul des tailles des cadres d'appel (frame)
  • L'allocation des variables locales, temporaires, ou globales.
La version du code minijava fournie ne gèere pas l'allocation des variables de bloc. Pour avlir une gestion complète des variables locales de bloc dans le compilateur Minijava remplacer dans allocator.Allocator:
for (IRVar v  : m.getLocals() ) { 
par
for (IRVar v  : m.getScope().getAllVariables() ) {
Quelques remarques importantes
  • Minijava ne contient pas l'accès explicite aux champs d'un objet autrement que sur "this". Dans la grammaire, le seul usage du token DOT est l'appel d'une méthode. Les champs d'un objet ne sont accédés que sur la variable this qui est toujours dans le registre "$a0"
  • La taille d'un objet de classe est définie en prenant la somme des champs de la classe et de ces ancêtres. De même pour le calcul des offsets permettant d'accéder aux champs.
  • Les champs d'objet sont accédés sous la forme offset($a0)
  • Seul la méthode main() contient des variables globales (temporaires). Elles sont référencées par offset($gp)
  • Les variables locales (temporaires ou du programme) sont enregistrées dans le cadre d"appel
  • Les tailles des cadres d"appel (framesize) sont calculées à partir d'une partie fixe (sauvegarde des registres) et incrémentées pour chaque déclaration de variable locale
  • Les variables locales (=presque toutes les variables) sont accédées par offset($fp)
  • Seul les paramètres des méthodes utilisent des registres

Écrire par étapes (milestones) la classe IR2MIPS pour réaliser la compilation du langage Minijava. Enfin le bout du tunnel!!!
  • Analyser la structure de la classe IR2MIPS : "Visiteur", Helpers, gestion des appels "spéciaux" (_system_*), ...
    • L'accès aux variables de l'IR (utilisation de allocator) est réalisé avec les méthodes regLoad(), regStore()
    • Les registres "$v0" et "$v1" sont librement accessibles pour les calculs temporaires dans le traitement de chaque instruction IR.
    • La gestion des instructions QParam est opérationnelle. Les paramètres sont accessibles dans un QCall avec les méthodes getArgs et checkArgs.
    • ...
  • Milestones :
    • Traduction de l'instruction QAssign(+,*).
      • Charger les 2 arguments de QAssign dans $v0 et $v1
      • Effectuer l'opération avec résultat dans $v0
      • Enregistrer $v0 dans le résultat de QAssign
    • Traduction de l'instruction QNew.
      • Sauvegarder le registre $a0
      • Charger dans $a0 la taille de l'objet à instancier (cf. Allocator.classSize())
      • Appeler la fonction _new_object du "Runtime MIPS"
      • Enregistrer $v0 (résultat de _new_object) dans le résultat de QNew
      • Restaurer $a0
      • NB : On applique ici une convention d'appel simplifiée
    • Construire la convention d'appel : QLabelMethod et QReturn pour l'appelé, QCall et QParam pour l'appelant.
      • QLabelMethod : sauvegarder les registres adéquats ($ra,$s0,$s1,...)
      • QReturn : charger dans $v0 la valeur de retour et restaurer les registres adéquats
      • QCall :
        • Sauvegarder les registres adéquats ($fp,$a0,$a1,$a2,$a3,$t0,$t1,...)
        • Charger les (maximum 4) paramètres dans les registres $a0,$a1,$a2,$a3. Attention : utiliser ici la méthode regLoadSaved pour éviter l'écrasement des paramètres d'appel (exemple de pb : f(a,b) { f(b,a); } ))
        • Créer le cadre d'appel : positionner le framepointer $fp au sommet de la pile $sp et avancer la pile de la taille de la frame Allocator.frameSize()
        • Effectuer l'appel : jal
        • Restaurer la pile avec la valeur de $fp (normalement inutile !)
        • Enregistrer $v0 comme résultat du QCall et restaurer les registres adéquats
    • et le reste : QAssign*, QCopy, QLabel et QJump*
      Aide-toy, le Ciel t’aidera. — (Jean de la Fontaine, Le Chartier embourbé, dans Fables, livre sixième, 1692-94)
Exemple de résultat : . Et un compilateur qui marche ! :

Valider le fonctionnement du compilateur sur les exemples du dossier ./Exemple/* et avec un fichier input.txt à construire à cet effet.

Pour le test sur différents fichiers, il peut être utile de travailler en ligne de commande :
  • la cible "jar" de make ou de ant permet de créer une archive jar exécutable du compilateur.
  • L'exécution du compilateur se fait alors avec : java -jar Run.jar fichier1 fichier2 ...
  • On teste ensuite chaque fichier assembleur produit avec mars fichier.mips

L'objectif est de compléter la génération de code de l'étape précédente. Afin d'éviter de casser le compilateur qui marche (normalement !), on modifiera les fonctionnalités en créant une nouvelle classe IRMIPSPlus qui hérite de la classe IR2MIPS et redéfinira (Override) uniquement les méthodes visit() que l'on veut améliorer ou étendre.
On adaptera dans CodeGen, l'appel à IR2MIPS ou IR2MIPSPlus et si besoin on peut modifier certains private de la classe IR2MIPS en protected.

Un squelette pour IR2MIPSPus :

Supprimer la contrainte sur le nombre d'arguments des méthodes. La classe allocator.Allocator implémente déjà la convention d'appel, et définit l'accès aux arguments 4 à N (0($fp),4($fp),,,). Il faut uniquement modifier les séquences d'appel et de sortie au niveau de l'appelant.
  • Modifier le traitement de l'instruction QCall pour accepter un nombre quelconque d'arguments dans la langage Minijava.
  • Vérifier que le compilateur marche encore pour une fonction récursive comme factorielle ou Ackermann.
  • Vérifier le fonctionnement avec par exemple :
    public int f (int a, int b, int c, int d, int e) { // f (4,1,2,3,4)=10
      int res;     
      if (a<1)  res=0;
      else res= e + this.f(a-1,e,b,c,d);
      return res;
    }	    
cf. question suivante

Le schéma simple dans la classe IR2MIPS qui consiste à toujours charger dans les registres "$v0" ou "$v1" pour faire ensuite des instructions MIPS ne tient pas compte du fait que les variables chargées sont éventuellement déjà dans des registres.
L'objectif de cette question est donc d'éviter d'utiliser les registres "v0" ou "v1" quand les variables de l'instruction IR (arg1,arg2,result) sont déjà dans des registres utilisables directement par les instruction MIPS.

On définit le helper suivant :
  private String tmpReg(IRVar v, String defReg) {
    String reg = allocator.access(v).getRegister();
    return (reg==null)? defReg:reg;
  }
      
Le code String r0=tmpReg(var,"$v0"); regLoad(r0,var); produira :
  • exactement le comportement de l'étape 5, si var n'est pas dans un registre (regLoad("$v0",var))
  • une optimisation (aucune instruction) si var est dans un registre (=r0)
On peut ainsi réaliser les instructions MIPS avec des "registres virtuels" r0 et r1 à la place de $v0 et $v1.

Une partie du schéma optimisé reste ouverte, c'est la façon de mapper les 3 Variables potentielles d'une instruction IR dans les 2 registres r0 et r1. Souvent le choix r0=tmpTeg(q.result,"$v0") est judicieux ou moins risqué.

Pour valider cette partie, on rappelle que seuls les paramètres d'appel d'une méthode sont dans des registres. Il convient donc de construire un exemple ad hoc pour observer l'optimisation Et un compilateur plus mieux qui marche :
...bonus rendu final ..
Les étapes précédentes fournissent un compilateur complet mais qui ne traite pas l'ensemble du langage Minijava. Il s'agit donc dans cette dernière étape, d'intégrer dans l'ensemble des phases du compilateur la gestion des tableaux d'entier du langage Minijava. L'énoncé qui suit donne quelques indications mais l'énoncé réel de cette étape est dans toutes les étapes qui précédent et dans le memento Minijava.
Quelques suggestions avant de démarrer :
  • Vérifier que le compilateur de départ est raisonnablement correct (cf. dernière question de l'étape 5)
  • Faire un commit/backup/mail_a_1_ami pour ne pas casser le "compilo qui marche"
  • Prévoir éventuellement des tests simples de non-régression (un petit script qui compile N fichiers de tests, et imprime le résultat de l'exécution avec mars)

Un token et cinq règles de grammaire à ajouter dans les spécifications Jflex et Cup.
Quatre nouveaux nœuds à utiliser dans l'AST :syntax.ast.*Array*.
Ne pas oublier de fixer une priorité pour les tokens "[]". (Bonus : tester aussi le comportement sans cette priorité)

La table de symbole est inchangée à part l'existence d'un type main.TYPE.INT_ARRAY.
Le contrôle de type doit traiter les 4 nouveaux nœuds de l'AST

Les 4 nouveaux nœuds de l'AST correspondent à 4 nouvelles instructions de la Représentation Intermédiaire : QAssignArrayFrom, QAssignArrayTo, QLength, QNewArray

Traduire les 4 nouvelles instructions IR dans IR2MIPS.

L'instanciation des tableaux utilisera la fonction _new_object du Runtime MIPS avec le schéma :
_new_object  avec IN  $a0 = 4 * LENGTH + 4
LENGTH == taille du tableau : enregistré a l'offset 0 du tableau

        [-- 4 --] [-------------- 4 * LENGTH ---------------------]
       +---------+--------+-- .. --+--------+-- .. --+-------------+
       | LENGTH  |   [0]  |   ..   |   [i]  |   ..   |  [LENGTH-1] |
       +---------+--------+-- .. --+--------+-- .. --+-------------+
       ^                           ^
      $v0	                   $v0 + 4 + 4 * i

Une variable tableau proprement dite est gérée de façon transparente par Allocator pour qui toutes les variables sont des valeurs sur 4 octets aussi bien pour les types primitifs (entier, booléen) que pour les types références (instance de classe ou tableau)

Compléter la question précédente en ajoutant le contrôle de bornes à l'exécution sur les tableaux.
  • Utiliser l'instruction MIPS sltu $t0, $t1, $t2 (?!?).
  • Gérer une sortie en erreur en complétant le Runtime MIPS (LinkRuntime) sous la forme :
    _array_out_of_bound :
           .data
    _err_msg:    .asciiz "OutOfBounds Exception " 
           .text
           la   $a0, _err_msg
           li   $v0,  4
           syscall // print message
           li $v0, 10
           syscall // exit
    

Valider le bon fonctionnement du compilateur. Fournir dans le rendu final, un fichier de test "input.txt" utile.

Ajouter au langage Minijava, les tableaux de booléens.
  • Ajouter un nom de type dans main.Type et une règle dans la spécification CUP.
  • Compléter le contrôle de type sur les tableaux (entier ou booléen)
  • et cela marche !! (ou pas !!!)

Le travail à rendre est une archive ".tar.gz" du compilateur complet. En plus des fonctionnalités dont le corrigé est fourni, le compilateur intègre :
  • La gestion des tableaux d'entiers
  • La fonction sémantique "Undefined" pour les variables, les classes et les méthodes
  • Un fichier de test "input.txt" qui couvre les fonctionnalités du compilateur et qui est valide pour le compilateur
  • (Bonus) Intégrer les tableaux de booléens dans le langage Minijava.
  • (Bonus) Étape 6 : améliorations de la génération MIPS.

Consignes :
  • Faire le ménage (make clean, ou ant clean) avant de construire l'archive
  • Exclure le dossier lib de l'archive si ce n'est pas un lien symbolique
  • Faire un fichier Readme avec le nom des auteurs, et les commentaires utiles pour le correcteur (on a fait comme il fallait mais ça ne marche pas, on n'a pas fait comme il fallait mais ça marche mieux!,....)
Attendez, c'est en train de compiler !!!
Un exemple original pour Minijava avec tableau d'entiers :
CSC 4536, TELECOM SudParis, P. Hennequin,Last modified: Dec 2019