jeudi 27 décembre 2018

C5-Sous-programmes MIPS

Les Sous-programmes MIPS

 1 Introduction

   Les programmes mis en œuvre dans les chapitres précédents sont très longs et nécessitent beaucoup de code. Par exemple, chaque programme doit utiliser le service syscall 10 pour quitter un programme. Il serait bien d’abstraire la méthode de sortie du programme une fois, puis d’utiliser cette abstraction dans chaque programme. Ceci est similaire aux sous-programmes qui existent dans tous les langages de programmation et nommés comme : fonctions, procédures ou méthodes.
    Le concept de sous-programme permet de créer un groupe d'instructions en langage assembleur MIPS pouvant être appelées pour effectuer une tâche.

2 Sous-programme de Sortie (Quitter)

    Le premier sous-programme à introduire s'appelle Quitter et appelle le service syscall 10 pour quitter le programme. Comme il ne sera pas nécessaire de revenir au programme appelant, ce sous-programme peut être créé en créant une étiquette Quitter et en insérant du code qui configure l'appel à syscall et l'exécute.

# Fichier : Programme- 5-2
###########################
.            .text
main:
             # Ecrire ("Bonjour les Sous-programmes …")
             li $v0, 4
             la $a0, Msg
             syscall

             # Appel du Sous-programme (Quitter) pour sortir
             jal Quitter

            .data
Msg:    .asciiz "\n Bonjour les Sous-programmes …"

# Sous-programme: Quitter
# Entrées: Aucune
# Sorties : Aucune
             .text
Quitter:
             li $v0, 10
             syscall

    Ce programme a deux sections .text. En effet, le sous-programme Quitter ne fait pas partie du programme principal et existe donc dans une autre partie du fichier. 

3 Les Sous-programmes : EcrireMsg, EcrireNum, LingneVide et LireNum

   Le sous-programme EcrireMsg va afficher un message (une chaine de caractères) à l’écran. Ce sous-programme nécessitera qu'un paramètre d'entrée soit transmis au programme, l'adresse de la chaîne à imprimer. Par conventions les registres $a0 .. $a4 sont utilisés pour transmettre des paramètres d'entrée à un programme. Le registre $a0 sera utilisé pour le paramètre d'entrée de ce sous-programme. Le sous-programme chargera alors un 4 dans $v0, invoquera syscall et retournera au programme principal.
       Le sous-programme EcrireNum va imprimer d'abord un message (une chaine de caractères) pour indiquer à l'utilisateur ce qui est en train d'être écrit, puis l'entier est  affiché. Dans ce sous-programme, il y a deux paramètres d'entrée: l'adresse de la chaîne à imprimer, qui est stockée dans $a0; et la valeur entière à imprimer, qui est stockée dans $a1 par conventions.
      Le sous-programme LingneVide va imprimer le caractère de retour à ligne (« \n ») sur la console de l'utilisateur.
      Le sous-programme suivant LireNum pour la lecture d’une valeur entière, montre comment renvoyer une valeur à partir d'un sous-programme. Par conventions les registres $a0 .. $a3 sont utilisés pour transmettre des valeurs à un sous-programme et les registres $v0 .. $v1 sont utilisés pour retourner des valeurs.

# Fichier : Programme-5-3.asm
#############################
            .text
main:
             # Lire une valeur entière
             la $a0, MsgLire
             jal LireNum
             move $s0, $v0

             # Ecrire  la valeur entière lue
             la $a0, MsgRes
             move $a1, $s0
             jal EcrireNum

             # Retour à la ligne
             jal LingneVide

             # Ecrire la chaine de caractère MsgFin 
             la $a0, MsgFin
             jal EcrireMsg

             # Appel du Sous-programme (Quitter) pour sortir
             jal Quitter

             .data
MsgLire: .asciiz "Entrer un entier ? "
MsgRes: .asciiz "Vous avez entré: "
MsgFin : .asciiz "Fin du programme. "

# Sous-programme: EcrireNum
# Entrées: $a0 - Adresse de la chaine à écrire.
#                $a1 – Valeur de l’entier à ecrire.
# Sorties : Aucune
             .text
EcrireNum:
             # Imprime la chaine. L’adresse de la chaine est dans $a0
             li $v0, 4
             syscall

             # Imprime l’entier. La valeur entière est dans $a1, on doit
             # d’abord la mettre dans $a0.
             move $a0, $a1
             li $v0, 1
             syscall

             # Retour au programme principal
             jr $ra

# Sous-programme: LireNum
# Entrées: $a0 - L’adresse de la chaine à écrire.
# Sorties : $v0 – La valeur entière saisie.
             .text
LireNum:
             # Imprime la chaine (MsgLire), qui est dans $a0
             li $v0, 4
             syscall

             # lit la valeur entière dans $v0.
             li $v0, 5
             syscall
             # Retour au programme principal 
             jr $ra

# Sous-programme: EcrireMsg
# Entrées: $a0 - Adresse de la chaine à écrire.
# Sorties : Aucune
             .text
EcrireMsg:
             addi $v0, $zero, 4
             syscall
             jr $ra

# Sous-programme: LingneVide
# Entrées: Aucune
# Sorties : Aucune
             .text
LingneVide:
             li $v0, 4
             la $a0, __RL
             syscall
             jr $ra

             .data
__RL:    .asciiz "\n"

# Sous-programme: Quitter
# Entrées: Aucune
# Sorties : Aucune
             .text
Quitter:
             li $v0, 10
             syscall
   

4 Créer un fichier « outils.asm »

    La dernière étape de l'ajout des sous-programmes développés dans ce chapitre consiste à les mettre dans un fichier séparé afin que tout programme que vous écrivez puisse les utiliser. Pour ce faire, créez un fichier appelé « outils.asm » et copiez tous les sous-programmes dans ce fichier.
    Le fichier entier « outils.asm » est décrit ci-dessous pour montrer à quoi il devrait ressembler une fois terminé.

# Fichier: outils.asm
# Objectif: Définir les utilitaires qui seront utilisés dans les programmes MIPS.
#
# Liste des sous-programmes:
# EcrireNum - Imprime une chaîne avec un entier sur la console
# LireNum - Invite l'utilisateur à entrer une nombre entier
# EcrireMsg - Imprime une chaîne sur la console
# LingneVide - Imprime un nouvelle ligne vide sur la console
# Quitter - Appelez syscall avec le service 10 pour quitter le programme
#
# Historique des modifications
# 15/12/2018 - Version initiale

# Sous-programme: EcrireNum
# Entrées: $a0 - Adresse de la chaine à écrire.
#                $a1 – Valeur de l’entier à ecrire.
# Sorties : Aucune
             .text
EcrireNum:
             # Imprime la chaine. L’adresse de la chaine est dans $a0
             li $v0, 4
             syscall

             # Imprime l’entier. La valeur entière est dans $a1, on doit
             # d’abord la mettre dans $a0.
             move $a0, $a1
             li $v0, 1
             syscall

             # Retour au programme principal
             jr $ra

# Sous-programme: LireNum
# Entrées: $a0 - L’adresse de la chaine à écrire.
# Sorties : $v0 – La valeur entière saisie.
             .text
LireNum:
             # Imprime la chaine (MsgLire), qui est dans $a0
             li $v0, 4
             syscall

             # lit la valeur entière dans $v0.
             li $v0, 5
             syscall
             # Retour au programme principal 
             jr $ra

# Sous-programme: EcrireMsg
# Entrées: $a0 - Adresse de la chaine à écrire.
# Sorties : Aucune
             .text
EcrireMsg:
             addi $v0, $zero, 4
             syscall
             jr $ra

# Sous-programme: LingneVide
# Entrées: Aucune
# Sorties : Aucune
             .text
LingneVide:
             li $v0, 4
             la $a0, __RL
             syscall
             jr $ra

             .data
__RL:    .asciiz "\n"

# Sous-programme: Quitter
# Entrées: Aucune
# Sorties : Aucune
             .text
Quitter:
             li $v0, 10
             syscall
   
     Finalement, les sous-programmes seront placés dans un autre fichier et inclus si nécessaire par la directive: .include "fichier_des_sous_programmes.asm"
     Le programme MIPS suivant est le résultat final de ce chapitre. Assurez-vous que le fichier contenant les sous-programmes « outils.asm » se trouve dans le même répertoire que le programme principal « Programme-5-3.asm ». Ouvrez « Programme-5-3.asm » dans la fenêtre d'édition de MARS, assemblez  et exécutez  ce dernier.

# Fichier : Programme-5-4.asm
# Objectif : Illustrer la mise en œuvre et l’appel de sous-programmes
###########################################################
.text
.globl main
main:
             # Lire une valeur entière
             la $a0, MsgLire
             jal LireNum
             move $s0, $v0

             # Ecrire  la valeur entière lue
             la $a0, MsgRes
             move $a1, $s0
             jal EcrireNum

             # Retour à la ligne
             jal LingneVide

             # Ecrire la chaine de caractère MsgFin 
             la $a0, MsgFin
             jal EcrireMsg

             # Appel du Sous-programme (Quitter) pour sortir
             jal Quitter

.data
             MsgLire: .asciiz "Entrer un entier ? "
             MsgRes: .asciiz "Vous avez entré: "
             MsgFin : .asciiz "Fin du programme. "
.include  "outils.asm"

5 Appels de sous-programmes imbriqués (Utilisation d’une Pile)

     Une pile (LIFO : Last In First Out) est une mémoire qui se manipule via deux opérations : 
  •          PUSH : empiler un élément (le contenu d’un registre) au sommet de la pile.
  •          POP : dépiler un élément (et le récupérer dans un registre).

     Ces deux instructions n’existent pas en assembleur MIPS, mais elles peuvent être “simulées”.  En utilisant les instructions sw et lw.  En stockant l’adresse du sommet de pile dans le registre $sp (le pointeur de pile).
     Deux politiques de sauvegarde des registres :
  • Sauvegarde par l’appelant : le sous-programme appelant sauvegarde tous les registres sur la pile avant l’appel (pour le passage de paramètre et la sauvegarde de l'adresse de retour).
  • Sauvegarde par l’appelé : le sous-programme appelant suppose que tous les registres seront préservés par le sous-programme appelé.


Remarque: un sous-programme doit rendre la pile intacte.

Exemple : Le Sous-programme B qui appel  le sous-programme C

         . . .
B:     . . .                          # début Sous-programme B
         . . .
         # Empiler les paramètres dans la pile
         sw $s0,  0 ($sp)      # Sauvegarder $s0 dans la pile
         sw $s1, -4 ($sp)      # Sauvegarder  $s1 dans la pile
         sw $ra , -8 ($sp)      # Sauvegarder $ra (adresse de retour) dans la pile
         add $sp, $sp , -12   # Ajuster le sommet de la pile

         # appel du Sous-programme C
         jal C                        

         # Dépiler les paramètres de la pile
         lw $ra , 4 ($sp)      # restaurer $ra (adresse de retour au programme principal) de la pile
         lw $s1, 8 ($sp)       # restaurer $s1  de la pile
         lw $s0, 12($sp)      # restaurer $s0  de la pile
         add $sp, $sp , 12   # Ajuster le sommet de la pile
         . . .
         . . .
         jr $ra                      # fin  du Sous-programme B
         . . .

C:     . . .                          # début Sous-programme C
         . . .