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
. . .