Version sans JavaScript, en lecture seule : les ateliers, les quiz et l’exécution du code n’y sont pas. Version complète · Catalogue · Se connecter

Support de cours · chapitre 5

Sous-programmes et pile

Découper un programme en sous-programmes réutilisables, grâce à la pile qui mémorise les adresses de retour, les registres à préserver et les paramètres.

La pile

Principe

La pile est une zone de mémoire où l'on range des valeurs provisoirement : on les empile, puis on les dépile dans l'ordre inverse, comme une pile d'assiettes. Le registre A7, aussi appelé SP (stack pointer), contient l'adresse du sommet de la pile.

Sur le 68000, la pile descend : empiler fait reculer SP. On se sert des modes du chapitre 3 :

OpérationInstructionEffet
empiler D0MOVE.W D0,-(SP)SP recule de 2, puis D0.W est rangé à l'adresse SP
dépiler dans D0MOVE.W (SP)+,D0le mot à l'adresse SP va dans D0, puis SP avance de 2

Une règle d'or

Tout ce qui est empilé doit être dépilé, dans l'ordre inverse, avant de quitter le morceau de programme qui l'a empilé. Une pile déséquilibrée, c'est un programme qui plante plus loin, loin de l'erreur.

Les sous-programmes

Appeler et revenir

Un sous-programme est un morceau de code qu'on appelle depuis plusieurs endroits, comme une fonction.

  • BSR ETIQ (branch to subroutine) empile l'adresse de retour (l'adresse de l'instruction qui suit le BSR, un mot long), puis saute à ETIQ. JSR fait la même chose avec n'importe quel mode d'adressage.
  • RTS (return from subroutine) dépile un mot long dans PC : l'exécution reprend juste après le BSR.

C'est pour cela que la règle d'or compte : si le sous-programme laisse quelque chose sur la pile, RTS dépile cette valeur à la place de l'adresse de retour, et le programme saute n'importe où.

Préserver les registres

Un sous-programme utilise des registres ; l'appelant aussi. Convention : un sous-programme rend intacts les registres qu'il ne sert pas à renvoyer. Il les sauve en entrant et les restaure en sortant. MOVEM (move multiple) le fait en une instruction :

SOUSPROG MOVEM.L D1-D3/A0,-(SP)  ; sauver D1, D2, D3 et A0
         ...                     ; le travail
         MOVEM.L (SP)+,D1-D3/A0  ; les restaurer
         RTS

Passer les paramètres

Par registres. Le plus simple : l'appelant range les paramètres dans des registres convenus, et le résultat revient dans un autre. Il faut l'écrire en tête du sous-programme.

* STRLEN : longueur d'une chaîne terminée par 0
* entrée : A0 = adresse de la chaîne
* sortie : D0.L = longueur ; les autres registres sont préservés
STRLEN  MOVE.L  A0,-(SP)    ; A0 va bouger : le sauver
        MOVEQ   #0,D0
BCL     TST.B   (A0)+
        BEQ     FIN
        ADDQ.L  #1,D0
        BRA     BCL
FIN     MOVE.L  (SP)+,A0    ; le restaurer
        RTS

Appel :

        LEA     MESSAGE,A0
        BSR     STRLEN      ; D0 <- longueur de MESSAGE

Par la pile. Quand les paramètres sont nombreux, ou pour écrire comme un compilateur, l'appelant les empile avant le BSR. Dans le sous-programme, ils sont sous l'adresse de retour : le dernier empilé est à 4(SP), le précédent à 6(SP) (pour des mots). Au retour, l'appelant les retire en avançant SP.

* appel : SOMME(3, 4)
        MOVE.W  #3,-(SP)    ; premier paramètre
        MOVE.W  #4,-(SP)    ; second paramètre
        BSR     SOMMEP
        ADDQ.L  #4,SP       ; retirer les deux paramètres
        ...                 ; D0.W vaut 7

* SOMMEP : D0.W <- somme des deux mots empilés
SOMMEP  MOVE.W  4(SP),D0    ; second paramètre (sous l'adresse de retour)
        ADD.W   6(SP),D0    ; premier paramètre
        RTS

La pile, juste après le BSR :

AdresseContenu
SPadresse de retour (mot long)
SP + 44, le second paramètre
SP + 63, le premier paramètre

La récursivité

Un sous-programme peut s'appeler lui-même. Chaque appel a besoin de ses propres valeurs : on les range sur la pile, qui en garde une par appel en cours.

* FACT : D1.L <- n!, pour n = D0.W, avec n <= 9
FACT    TST.W   D0
        BNE     RECUR
        MOVEQ   #1,D1       ; 0! = 1
        RTS
RECUR   MOVE.W  D0,-(SP)    ; sauver n : l'appel suivant va changer D0
        SUBQ.W  #1,D0
        BSR     FACT        ; D1 <- (n-1)!
        MOVE.W  (SP)+,D0    ; retrouver n
        MULU.W  D0,D1       ; D1 <- n * (n-1)!
        RTS

Pour $n = 3$, trois valeurs de $n$ sont empilées (3, 2, 1) avant d'atteindre $0! = 1$, puis dépilées une à une, chacune multipliant le résultat. La limite $n \le 9$ vient de MULU.W, qui ne multiplie que le mot bas de D1 : il faut que $(n-1)!$ tienne sur 16 bits, soit au plus 65535, et $8! = 40320$ tient, $9! = 362880$ non.

À essayer

La factorielle, avec une saisie : le programme demande $n$ (service 4 de TRAP #15) et affiche $n!$. Exécutez-le pas à pas et suivez A7 : il descend de 6 octets à chaque appel récursif (le mot de $n$ et l'adresse de retour), puis remonte. Essayez $n = 10$ : le résultat est faux, pour la raison donnée plus haut.

        ORG     $1000
START   LEA     INVITE,A1
        MOVE.B  #14,D0          ; service 14 : afficher la chaîne (A1)
        TRAP    #15
        MOVE.B  #4,D0           ; service 4 : lire un nombre dans D1.L
        TRAP    #15
        MOVE.W  D1,D0           ; n
        BSR     FACT            ; D1 <- n!
        MOVE.B  #3,D0           ; service 3 : afficher D1.L
        TRAP    #15
        SIMHALT

* FACT : D1.L <- n!, pour n = D0.W, avec n <= 9
FACT    TST.W   D0
        BNE     RECUR
        MOVEQ   #1,D1           ; 0! = 1
        RTS
RECUR   MOVE.W  D0,-(SP)        ; sauver n
        SUBQ.W  #1,D0
        BSR     FACT            ; D1 <- (n-1)!
        MOVE.W  (SP)+,D0        ; retrouver n
        MULU.W  D0,D1           ; D1 <- n * (n-1)!
        RTS

INVITE  DC.B    'n = ',0
        END     START