Environnements d'exécution Notions de base Spécificités des langages sources Organisation de la mémoire Stratégies d'allocation mémoire Accès aux noms non locaux Passage de paramètres
Objectifs Décrire la relation entre le programme source et l'exécution Exemple de notion statique : les noms des variables (font partie du programme source) Exemple de notion dynamique : les adresses des variables (existent pendant l'exécution) Décrire le moteur d'exécution Le code que le système d'exploitation lance pour exécuter le programme compilé Langages C, Java, Pascal, Lisp, Fortran
Notions de base (1/2) Procédures ou fonctions Définies par l'association d'un identificateur, le nom, et d'une instruction, le corps Parfois on dit fonction quand elle renvoie une valeur et procédure sinon ; nous ne ferons pas la différence Paramètres formels Dans la définition de la fonction Paramètres effectifs Dans un appel de la fonction Activation d'une fonction Période qui va de l'appel d'une fonction (avec éventuellement des paramètres réels) à son retour
Exemple program sort(input, output) ; var a : array[0..10] of integer ; procedure readarray ; var i : integer ; begin for i := 1 to 9 do read(a[i]) end ; function partition(y, z : integer) : integer ; begin ... end ; procedure quicksort(m, n : integer) ; begin if (n>m) then begin i := partition (m, n) ; quicksort(m, i - 1) ; quicksort (i + 1, n)
Exemple end end ; begin a[0] := - 9999 ; a[10] := 9999 ; readarray ; quicksort(1, 9) end.
Notions de base (2/2) Fonctions récursives Fonction dont une activation peut commencer alors qu'une autre est en cours Arbre d'exécution Les noeuds sont les activations des fonctions avec leurs paramètres Chaque fils d'un noeud correspond à un appel Pile d'exécution La pile des activations en cours à un instant donné Portée d'une déclaration Noms locaux, noms globaux
Arbre d'exécution sort quicksort(1, 9) readarray partition(1, 9)
Pile d'exécution ... ... sort quicksort(1, 9) readarray partition(1, 9) quicksort(1, 3) quicksort(5, 9) partition(1,3) partition(5, 9) quicksort(1, 0) quicksort(5, 5) ... ... sort quicksort(1, 9) quicksort(1, 3) fond de pile sommet de pile
Notions statiques et dynamiques Exemple : passage d'un nom de variable à une valeur liaison état nom adresse valeur Si l'environnement associe une occurrence du nom x à l'adresse s, on dit que x est lié à s Un nom peut être lié à plusieurs adresses Une occurrence d'un nom peut être liée à plusieurs adresses (fonction récursive) Une adresse peut contenir plusieurs valeurs successivement
Notions statiques et dynamiques statique dynamique connu à la compilation dépend de chaque exécution définition d'une fonction activations d'une fonction déclaration d'un nom liaisons d'un nom portée d'une déclaration durée de vie d'une liaison
Spécificités des langages L'organisation d'un compilateur dépend des réponses à des questions sur le langage source Y a-t-il des fonctions récursives ? Comment sont traités les noms locaux au retour des fonctions ? Une fonction peut-elle utiliser des noms non locaux ? Comment sont passés les paramètres ? Les fonctions peuvent-elles être passées en paramètre ? être renvoyées comme valeur ? L'allocation de mémoire peut-elle être dynamique ? La libération doit-elle être explicite ?
Organisation de la mémoire Zone utilisée en mémoire pour l'exécution d'un programme Code exécutable (taille statique) Données statiques : dont les adresses sont compilées dans le code Pile d'exécution Le tas est l'emplacement de toutes les autres informations : en C, la mémoire allouée dynamiquement Les adresses dans la pile sont des adresses relatives (offset) par rapport au sommet de la pile code données statiques pile tas
Enregistrements d'activation Zone de mémoire empilée à l'appel d'une fonction et dépilée au retour fond de pile Lien de contrôle : pointe sur l'enregistrement d'activation appelant Lien d'accès : accès aux variables non locales Etat machine : valeurs des registres au moment de l'appel Zone temporaire : pour le calcul des expressions valeur renvoyée paramètres effectifs lien de contrôle lien d'accès (en Pascal) sauvegarde de l'état de la machine variables locales zone temporaire sommet de pile
Disposition des données locales Adresses calculées à la compilation Unité minimale d'adressage L'octet (byte) Pour certaines opérations : le mot machine, souvent 4 octets Espace laissé vide : remplissage (padding) Placement d'un objet Octets consécutifs (la taille dépend du type) L'adresse est celle du premier octet Pendant l'analyse des déclarations Adresses relatives Par rapport à un point de référence dans l'enregistrement d'activation
Exemple La taille de chaque type C dépend de la machine machine type char short int long float taille (bits) 8 16 32 alignement (bits) taille 24 48 64 alignement 1 2 Alignement : sur la machine 1, si un char est suivi d'un short, on perd 1 octet Taille d'un pointeur : sur la machine 2, 24 bits pour le mot et 6 pour 1 bit parmi 64, donc 30 bits
Allocation statique La stratégie d'allocation mémoire la plus simple Toutes les liaisons sont permanentes pendant toute l'exécution Les valeurs des noms locaux persistent d'un appel au suivant Les adresses des données sont statiques L'emplacement des enregistrements d'activation est statique Limitations La taille de toutes les données est statique La récursivité est interdite La création dynamique de structures de données est interdite
Exemple Un programme en Fortran qui - lit une ligne et la copie dans un tampon BUFFER - copie BUFFER dans BUF jusqu'à rencontrer un espace, puis affiche BUF (programme principal) Exemple entrée : demain matin sortie : demain La fonction PRDUCE - lit la ligne et la copie dans BUFFER (premier appel) - puis lit dans BUFFER un caractère par appel Le programme principal écrit dans BUF puis affiche BUF Attention au choc culturel...
Exemple PROGRAM CNSUME CHARACTER * 50 BUF INTEGER NEXT CHARACTER C, PRDUCE DATA NEXT /1/, BUF /' '/ 6 C = PRDUCE() BUF(NEXT:NEXT) = C NEXT = NEXT + 1 IF ( C .NE. ' ' ) GOTO 6 WRITE (*, '(A)') BUF END
CHARACTER FUNCTION PRDUCE() CHARACTER * 80 BUFFER INTEGER NEXT SAVE BUFFER, NEXT DATA NEXT /81/ IF ( NEXT .GT. 80 ) THEN READ (*, '(A)') BUFFER NEXT = 1 END IF PRDUCE = BUFFER(NEXT:NEXT) NEXT = NEXT+1 END
L'instruction SAVE permet à PRDUCE de retrouver les valeurs de BUFFER et NEXT d'un appel à l'autre code de CNSUME code de PRDUCE enregistrement d'activation de CNSUME : BUF NEXT C enregistrement d'activation de PRDUCE : BUFFER code données statiques
Allocation en pile Les enregistrements d'activation sont empilés à l'appel de la fonction et dépilés au retour Les valeurs locales sont perdues Séquence d'appel réserve et remplit un enregistrement d'activation Séquence de retour restaure l'état de la machine avant l'appel pour reprendre l'exécution où elle en était
sauvegarde de l'état machine données locales temporaires valeur renvoyée paramètres lien d'accès sauvegarde de l'état machine données locales temporaires enregistrement d'activation de l'appelant lien de contrôle à la charge de l'appelant valeur renvoyée paramètres lien d'accès sauvegarde de l'état machine données locales temporaires enregistrement d'activation de l'appelé lien de contrôle sommet de pile
Séquences d'appel et de retour Séquence d'appel L'appelant évalue les paramètres Il enregistre l'adresse de retour et la valeur du sommet de pile dans l'enregistrement d'activation de l'appelé Il incrémente le sommet de pile L'appelé sauvegarde les valeurs des registres Il initialise ses données locales Séquence de retour L'appelé place la valeur à renvoyer Il restaure le sommet de pile et les autres registres et saute à l'adresse de retour L'appelant copie la valeur renvoyée
Allocation en pile Données de taille variable Les données de taille variable sont placées sur la pile au-dessus de l'enregistrement d'activation Leur adresse relative n'est pas connue à la compilation L'enregistrement d'activation contient des pointeurs vers ces données Les adresses relatives de ces pointeurs sont connues à la compilation On utilise un 2e pointeur de sommet de pile qui tient compte des données de taille variable (sommet)
sauvegarde de l'état machine lien d'accès sauvegarde de l'état machine lien de contrôle enregistrement d'activation de p pointeur sur A pointeur sur B tableau A tableaux de p tableau B lien d'accès sauvegarde de l'état machine données locales temporaires lien de contrôle enregistrement d'activation de q sommet de pile tableau A tableaux de q sommet
Allocation en pile Données de taille variable Au retour de q, la nouvelle valeur de Sommet est restaurée à partir de SommetPile en tenant compte de la taille des champs de sauvegarde, liens, paramètres et valeur de retour la nouvelle valeur de SommetPile est la valeur du lien de contrôle
Accès aux noms non locaux Dépend des règles de portée des variables dans le langage source Les règles qui relient les occurrences des variables à leurs déclarations Portée statique ou lexicale C, Java, Pascal, Ada Portée déterminée par le texte source du programme Portée dynamique Lisp, APL, Snobol Portée déterminée par les activations en cours
Structure de blocs Bloc --> { Déclarations Instructions } Les instructions peuvent contenir des blocs Une fonction peut contenir plusieurs blocs Portée statique La portée d'une déclaration faite dans B est incluse dans B Si un nom x n'est pas déclaré dans B (non local à B), une occurrence de x dans B est liée à la déclaration de x dans le plus petit bloc B' tel que - x est déclaré dans B' - B' contient B
Exemple B0 B1 B2 B3 main() { int a = 0 ; int b = 0 ; int b = 1 ; printf("%d %d", a, b) ; } int b = 3 ; B0 B1 B2 B3
Portée statique Réalisation : deux méthodes Allocation en pile On considère chaque bloc comme une fonction sans paramètres ni valeur de retour Allocation globale On regroupe toutes les variables déclarées dans les blocs de la fonction On peut lier à une même adresse deux variables dont les portées sont disjointes (a de B2 et b de B3)
Portée statique sans fonctions emboîtées Les noms sont de deux sortes : - locaux à une fonction - globaux, déclarés hors des fonctions Exemple : le langage C Les noms globaux sont alloués de façon statique Les noms locaux sont en pile, accessibles à partir du pointeur de sommet de pile Pas besoin de lien d'accès
Fonctions passées en paramètre Avec la portée statique sans fonctions emboîtées, on peut facilement passer une fonction en paramètre ou la renvoyer comme résultat #include <stdio.h> int m ; int f(int n) { return m + n ; } int g(int n) { return m * n ; } int b(int (*h)(int)) { printf("%\n", h(2)) ; } int main(void) { m = 0 ; b(f) ; b(g) ; }
Portée statique avec fonctions emboîtées (1/6) En Pascal, on peut déclarer une fonction à l'intérieur d'une autre Les emboîtements de fonctions forment un arbre statique sort readarray exchange(i, j) quicksort(m, n) Profondeur d'emboîtement 1 pour le programme principal On ajoute 1 pour chaque imbrication
Exemple program sort(input, output) ; var a : array[0..10] of integer ; x : integer ; procedure readarray ; var i : integer ; begin for i := 1 to 9 do read(a[i]) end ; procedure exchange(i, j : integer) ; begin x := a[i] ; a[i] := a[j] ; a[j] := x end ; procedure quicksort(m, n : integer) ; var k, v : integer ; function partition(y, z : integer) : integer ; var i, j : integer ; begin ... a ... v ... exchange(i, j) ; ... end ; begin ... end ; begin ... end .
Portée statique avec fonctions emboîtées (2/6) sort quicksort(m, n) readarray exchange(i, j) partition(y, z) Profondeur d'une variable Profondeur d'emboîtement de la fonction où elle est définie Une variable de profondeur v ne peut être utilisée que dans une fonction de profondeur f v Une fonction de profondeur g ne peut être appelée que par une fonction de profondeur f g - 1 ; f = g - 1 seulement si g est locale à f
Portée statique avec fonctions emboîtées (3/6) sort quicksort(m, n) readarray exchange(i, j) partition(y, z) Si une fonction g locale à f a un enregistrement d'activation dans la pile, alors f a un enregistrement d'activation au-dessous de celui de g (c'est l'EA le plus récent qui soit de profondeur inférieure à celle de g) L'enregistrement d'activation de f n'est pas forcément juste au-dessous de celui de g Le lien de l'EA de g vers l'EA de f est le lien d'accès
Portée statique avec fonctions emboîtées (4/6) sort quicksort(m, n) readarray exchange(i, j) partition(y, z) Si une variable est utilisée dans une fonction g, - elle est déclarée dans g ou dans une fonction h qui contient g - on la lie à un enregistrement d'activation présent dans la pile : l'enregistrement d'activation de h le plus récent - on accède à cet EA en remontant les liens d'accès - le nombre de liens d'accès est la différence de profondeur entre g et h
Portée statique avec fonctions emboîtées (5/6) sort quicksort(m, n) readarray exchange(i, j) partition(y, z) Si une variable a de profondeur p(a) est utilisée dans une fonction f de profondeur p(f), on trouve son adresse : - en remontant p(f) - p(a) liens d'accès - en utilisant l'adresse relative de a dans l'enregistrement d'activation obtenu Ces deux valeurs sont connues à la compilation Elles représentent la liaison de cette occurrence de a
Portée statique avec fonctions emboîtées (6/6) Calcul du lien d'accès dans la séquence d'appel Une fonction f appelle une fonction g Si p(f) = p(g) - 1 g est locale à f : le lien d'accès est égal au lien de contrôle Si p(f) p(g) on remonte p(f) - p(g) + 1 liens d'accès depuis l'enregistrement d'activation de f Ces calculs sont faits à la compilation
sort a, x sort a, x sort a, x sort a, x quicksort(1, 9) k, v quicksort(1, 9) k, v quicksort(1, 9) k, v quicksort(1, 9) k, v accès accès accès accès quicksort(1, 3) k, v quicksort(1, 3) k, v quicksort(1, 3) k, v accès accès accès partition(1, 3) i, j partition(1, 3) i, j accès accès exchange(1, 3) accès
Fonctions passées en paramètre program param(input, output) ; procedure b(function h(n : integer) : integer ; begin writeln(h(2)) end ; procedure c ; var m : integer ; function f(n : integer) : integer ; begin f := m + n end ; begin m := 0 ; b(f) end ; begin c end .
Fonctions passées en paramètre Calcul du lien d'accès à l'appel de f param c m accès b <f ; > k, v acc Pour appeler f on a besoin de l'adresse du code et du lien d'accès accès
Accès direct aux noms non locaux Une méthode plus rapide pour accéder aux noms non locaux Un tableau de pointeurs vers les enregistrements d'activation Pour chaque profondeur j, display[j] pointe vers l'enregistrement d'activation où sont placés les noms non locaux de niveau j A chaque appel on met à jour display Quand on empile un enregistrement de profondeur i, 1. sauvegarder display[i] dans le nouvel enregistrement 2. faire pointer display[i] sur le nouvel enregistrement Avant de dépiler, on restaure display[i]
sort sort sort sort d[1] d[1] d[1] d[1] d[2] d[2] d[2] d[2] qs(1, 9) qs(1, 9) qs(1, 9) qs(1, 9) d[2] d[2] d[2] d[2] d[3] d[3] qs(1, 3) qs(1, 3) qs(1, 3) d[2] d[2] d[2] pt(1, 3) pt(1, 3) d[3] d[3] ex(1, 3) d[2]
Portée dynamique Les liaisons des noms non locaux ne changent pas quand on appelle une fonction Un nom non local dans la fonction appelée est lié au même emplacement que dans la fonction appelante Réalisation On recherche l'emplacement des noms non locaux en suivant les liens de contrôle On n'a pas besoin de liens d'accès
Portée dynamique Résultats : portée statique 0.250 0.250 program dynamic(input, output) var r : real ; procedure show begin write(r : 5:3) end ; procedure small begin r := 0.125 ; show end ; begin r := 0.250 ; show ; small ; writeln ; end . Résultats : portée statique 0.250 0.250 portée dynamique 0.250 0.125
Passage des paramètres (1/5) Passage par valeur Les valeurs des paramètres effectifs sont passés à la procédure appelée Exemple : le langage C Réalisation Les paramètres formels sont traités comme des noms locaux Les paramètres effectifs sont évalués par l'appelant Ne modifie pas les valeurs dans l'enregistrement d'activation de l'appelant, sauf à travers des noms non locaux ou des pointeurs passés par valeur
Passage des paramètres (2/5) Pascal procedure echange(i, j : integer) ; var x : integer ; begin x := a[i] ; a[i] := a[j] ; a[j] := x end C swap(int * x, * y) { int temp ; temp = * x ; * x = * y ; * y = temp ; } main() int a = 1, b = 2 ; swap(& a, &b) ;
Passage des paramètres (3/5) Passage par référence Les adresses des paramètres effectifs sont passés à la procédure appelée Exemple en Pascal program reference(input, output) ; var a, b : integer ; procedure swap(var x, y : integer) ; var temp : integer ; begin temp := x ; x := y ; y := temp end ; a := 1 ; b := 2 ; swap(a, b) ; end .
Passage des paramètres (4/5) Passage par copie et restauration Les valeurs des paramètres effectifs sont passées à la procédure appelée Au retour, les nouvelles valeurs des paramètres sont copiées à leur adresse Exemple : Fortran
Passage des paramètres (5/5) Passage par nom Le corps de la fonction est substitué à l'appel Les paramètres sont substitués littéralement Exemples Algol Macros en C #define swapint(a, b) {int x = a ; a = b ; b = x ; } swapint(i, a[i]) affecte la valeur i à a[a[i]]