wiki:SujetTP3

ALMO TP n°3 - Génération de code avec GNU GCC : exécution sous MARS

Préambule

Les outils GNU (GNU is Not Unix) sont librement disponibles sur Internet. La chaîne de compilation GCC est très performante et donc très utilisée, dans le monde académique comme dans le monde industriel. Elle permet de générer des fichiers binaires exécutables à partir de code source en langage C. Nous vous proposons dans ce TP de mettre en œuvre le compilateur C croisé pour processeur MIPS32 : mipsel-mars-elf-gcc. On utilisera le simulateur MARS pour exécuter en mode instruction-par-instruction, le programme assembleur MIPS32 généré.

Remarque : lorsque l'on compile une application pour un processeur différent de celui qui exécute la compilation (on utilise ici un PC sous Linux avec un processeur de type x86 pour générer le code binaire d'un processeur MIPS32), on parle de compilation croisée.

1. Exécution du programme de tri écrit directement en assembleur

Dans cette première partie, vous devez saisir sous votre éditeur de texte préféré le programme de tri, écrit en assembleur dans le cadre du TD3, et l'exécuter sous MARS. Une démonstration vous sera demandée.

2. Compilation d'un fichier C exemple

Dans cette deuxième partie, on utilise les outils GNU pour générer automatiquement le programme assembleur à partir d'un programme écrit en C.

2.1. Configuration de la chaine de compilation croisée

  • Si vous ne l'aviez pas déjà fait, configurez votre environnement en suivant le manuel de configuration pour avoir l'accès à la chaine de compilation croisée.
  • Vérifier que le compilateur est bien disponible à l'aide de la commande :
$ mipsel-mars-elf-gcc --version

Vous devriez obtenir un message commençant par 'mipsel-mars-elf-gcc (GCC) 3.4.6'.

2.2. Test simple

  • Commencez par éditer un petit programme C, sans appel de fonction, dans le fichier 'exemple.c'.
int main(void) {
    int a, b;
    a = 24;
    b = 16;
    while (a != b) {
        if (a > b) {
            a = a - b;
        }
        else {
            b = b - a;
        }
    }
    return 0;
}
  • Que fait ce programme ?
  • Compilez ce fichier source 'exemple.c' de manière à obtenir le code assembleur correspondant dans le fichier exemple.s à l'aide de la commande :
$ mipsel-mars-elf-gcc -fomit-frame-pointer -ffreestanding -mips32 -S exemple.c

L'option -fomit-frame-pointer impose à GCC de n'utiliser que SP (Stack Pointer = $29) pour adresser les variables dans la pile. Par défaut, le compilateur utilise aussi le Frame Pointer. Comme cette technique n'a pas été utilisée en cours et dans les TD précédents, on évite de l'utiliser ici.

  • En éditant le fichier exemple.s, déterminez les noms des étiquettes ('labels') intervenant dans ce programme. En déduire comment GCC fait pour générer les noms d'étiquette.
  • Déterminez à quelles adresses sont stockées en mémoire les valeurs des deux variables locales a et b.
  • Identifiez le code permettant d'initialiser la variable a avec la valeur 24. Même question pour l'initialisation de b.
  • Déterminez comment le compilateur génère le code assembleur MIPS32 correspondant à l'instruction C 'while'. Quelles sont les étiquettes qui interviennent dans ce code ? En déduire la manière générale utilisée par GCC pour générer le code assembleur MIPS32 équivalent à l'instruction C 'while'.
  • Déterminez comment le compilateur génère le code assembleur MIPS32 correspondant à l'instruction C 'if'. Quelles sont les étiquettes qui interviennent dans ce code ? En déduire la manière générale utilisée par GCC pour générer le code assembleur MIPS32 équivalent à l'instruction C 'if'.
  • Le fichier exemple.s n'est pas directement utilisable par MARS :
    • MARS ne supporte pas toutes les directives définies par le langage d'assemblage du processeur MIPS32. Ce n'est pas important car la plupart des directives sont ignorées par MARS.
    • En revanche la directive .align est connue de MARSmais avec un usage restreint. .align n impose que la donnée ou l'instruction qui suit commence à la prochaine adresse 2n. MARS n'autorise pas cette directive dans une section .text, ce que fait gcc. Il faut donc éditer exemple.s et mettre la ligne .align en commentaire.
    • Enfin, GCC produit des instructions telle que j $31. L'instruction j prend une adresse littérale et non pas un registre. GCC suppose que j est une macro-instruction qui choisit la bonne instruction au moment de l'assemblage en fonction de son argument. MARS ne faisant pas cette transformation, vous devez remplacer à la main j $31 par jr $31?
  • Exécutez en pas à pas le code assembleur généré par GCC sous MARS, et vérifiez que la valeur calculée par ce programme est correcte en examinant le contenu des registres.
  • Si vous voulez exécuter l'ensemble vous devez ajouter une séquence de code assembleur pour l'affichage du résultat et la terminaison du programme, juste avant la restauration du pointeur de pile.
        lw    $4, ($sp)
        li    $2, 1
        syscall
        li    $2, 10
        syscall
    

3. Compilation du fichier tri.c et utilisation de MARS

  • Compilez le fichier 'tri.c' ci-dessous, de manière à générer automatiquement le code assembleur correspondant dans le fichier 'tri.s'. Ce programme est une version simplifiée (sans affichage) du programme de tri du TD3.
void echange(int * tab, int i, int j) {
    int tmp;
    tmp = tab[i];
    tab[i] = tab[j];
    tab[j] = tmp;
}

void tri(int * tab, int taille) {
    int valmax;
    int i, imax;

    // cas terminal : fin de recursivite
    if (taille < 2) {
        return;
    }

    // cas général :  taille > 1
    valmax = 0;
    for (i = 0; i < taille; i++) {
        if (tab[i] > valmax) {
            valmax = tab[i];
            imax = i;
        }
    } // fin for
    echange(tab, imax, taille - 1);
    tri(tab, taille - 1);
}

int main(void) {
    int tab[5] = { 3, 33, 49, 4, 23 };
    tri(tab, 5);
}
  • Eliminez les directives non supportées par MARS, lancez l'assemblage et le chargement en mémoire du programme sous MARS. Où est stocké ce tableau tab en mémoire. Exécutez le programme en mode pas à pas pour comprendre comment ce tableau est initialisé.
  • Utilisez la commande de MARS permettant de poser un point d'arrêt juste après l'appel à la procédure de tri, puis lancez une exécution du simulateur MARS en mode run, et vérifiez qu'après arrêt du simulateur le tableau est correctement trié.
  • Relancez une simulation en mode pas-à-pas, pour suivre le déroulement du programme, en particulier l'évolution de la pile et des passages de paramètres lors des appels à la fonction echange(). En supposant que le processeur exécute une instruction par cycle (ce qui est presque vrai pour un processeur RISC pipeline), déterminez le coût (en nombre de cycles) d'un appel à la fonction echange().
Last modified 5 years ago Last modified on Sep 4, 2019, 2:11:24 PM