• Aucun résultat trouvé

4.3 Définition d’une BSP-CAM

4.3.1 Machine abstraite CAM

4.3.3 De la CAM à la BSP-CAM . . . 54 4.4 Compilation de BSML . . . 55 4.4.1 Termes séquentiels . . . 55 4.4.2 Primitives parallèles . . . 55 4.4.3 Correction de la BSP-CAM . . . 57 4.4.4 Optimisation . . . 59

4.A Annexe, preuves des conditions . . . 61

C

OMPILERun programme consiste à traduire ce programme, dit code-source, en un autre programme, dit code-objet écrit dans un langage propre à être exécuté sur une machine (et dans notre cas, sur une machine parallèle). Le but de ce chapitre est de prouver la correction des mécanismes mis en jeu par un compilateur BSML.

4.1 Introduction

Les sémantiques données au précédent chapitre donnaient une vision sur le fonctionnement des programmes BSML. Néanmoins, le comment de l’exécution de ces programmes sur une machine BSP n’était pas précisé. Pour cela il nous faut compiler nos termes pour qu’ils soient exécutables.

Notre démarche est celle d’un premier pas vers la preuve d’un compilateur : nous ne voulons pas, pour l’instant, établir de propriété sur les mécanismes «bas niveau» du code-machine tels que la gestion de la mé-moire et des registres. Nous allons choisir comme langage objet, un langage suffisamment formel pour que nous puissions prouver la correction de la compilation de nos termes. Ce choix s’est porté vers une machine

abstraite (encore appelée machine virtuelle ou à environnement) car celle-ci est simple de compréhension,

suffisamment complexe pour être proche d’une machine abstraite réelle (utilisée par un compilateur) et le code des machines abstraites peut être traduit vers le code machine (dit natif) des ordinateurs modernes (le compilateur OCaml fournit un tel mécanisme).

4.2 Définition et correction d’une machine abstraite

La correction d’une machine exprime une relation entre le code-source (provenant du terme de départ) avec lequel on initialise la machine, et la valeur qu’on extrait de l’état terminal. La correction d’une machine

abstraite peut se traduire par : «la valeur résultante de l’exécution est la forme normale du programme source». Pour comprendre les mécanismes mis en œuvre par les instructions d’une machine abstraite et en interpréter les états intermédiaires, nous allons nous donner un formalisme générique pour le code-objet d’une machine quelconque. Pour ce faire, il nous faut expliquer ce qu’est une machine abstraite et ce que veut dire «compiler un programme fonctionnel» (et parallèle).

4.2.1 Définition d’une machine abstraite

Une machine abstraite est définie comme un automate déterministe dont les états décrivent la mémoire d’une pseudo-machine. Les transitions, elles, simulent l’exécution d’un programme sur cette machine abstraite.

Définition 18 (Machine abstraite [216]).

Une machine est une paire(E, →

E) où E est un ensemble d’états et →

E un fonction de transition (fonction partielle deE × E dans E). Nous notons s= (→

E (s)) par s →

E ssic ∈ E.

Il existe dans la littérature beaucoup de machines absraites différentes ; leur représentation varie d’une machine à une autre et d’un auteur à un autre. Toutefois, [216] a unifié leur présentation de la manière suivante :

1. Les instructions diffèrent d’une machine à une autre, mais pour chacune d’entre elles, le code est une liste (possiblement vide) d’instructions ;

2. Une fermeture est un morceau de code associé à un environnement (une substitution) et un environ-nement est une liste de fermetures ou de valeurs ;

3. Certaines machines utilisent une ou plusieurs piles de fermetures et de valeurs ;

4. La structure exacte des blocs varie suivant la machine considérée ; mais un bloc contient au moins toujours un morceau de code et un environnement ;

5. Un état de la machine abstraite est une liste de blocs.

La fonction de transition d’une machine abstraite est définie par cas sur le (ou les) bloc(s) courant(s) d’un état. Notons que, dans la plupart des cas, la transition ne dépend que de l’instruction de tête du code du bloc courant.

Nous ne décrirons, dans ce chapitre, qu’une seule machine abstraite : une CAM. Nous n’utilisons pas la ZAM [176], la machine abstraite de OCaml, car elle est bien trop compliquée pour notre propos. Les lecteurs intéressés peuvent, par une description formelle de cette machine (et sa preuve de correction dans l’assistant de preuve Coq), se référer à [127] et à [216] pour un inventaire de machines abstraites prouvées correctes (à la main) et une taxinomie des différentes machines abstraites existantes.

A cette définition de machine abstraite, nous devons ajouter un processus de compilation pour transformer un terme en un état d’une machine afin de démarrer l’exécution.

Définition 19 (Compilation [216]).

Soit une machine(E, →

E). La compilation est une fonction C : Λ → E. Un état s est dit initial s’il existe un

termee tel que s = C(e). Un état sest dit accessible s’il existe un état initials tel que s

E s.

De manière réciproque, il faut définir une fonction de «décompilation» pour nous permettre d’extraire de l’état final le résultat de l’exécution d’un programme. Pour se faire, il nous faut définir la notion de valeur d’une machine abstraite. Nous allons voir pourquoi, leλ-calcul n’est pas un langage suffisant pour exprimer

les valeurs des machines abstraites.

4.2.2 Retour sur les substitutions

Une fonction est transformée en un morceau de code fixe, accomplissant les actions inscrites dans son corps. Ce code reste inchangé pour toutes les invocations de la fonction au cours de l’exécution du programme. Cette fonction contient des variables qui peuvent être de deux sortes : ce sont des paramètres ou des variables libres (mais liées à l’extérieur du corps de la fonction). Les paramètres formels changent à chaque invocation de la fonction, tandis que les variables gardent la même valeur. Ceci amène à considérer les fermetures

51 4.2. DÉFINITION ET CORRECTION D’UNE MACHINE ABSTRAITE

comme des objets de «bas niveau» décrivant les fonctions. Une fermeture est donc un couple composé du code de la fonction et de l’ensemble des valeurs que prennent les variables libres de la fonction.

Leλ-calcul est insuffisant pour exprimer le code objet. Prenons par exemple, le terme suivant : (λx.λy.(x y))(λz.z)

Ce terme se réduit en(λy.((λz.z) y)). D’un point de vie sémantique, rien d’anormal. La variable liée a bien

été substituée par le terme donné en paramètre lors de l’application. Mais du point de vue de l’implantation, cela revient à dire que le code a été modifié pour créer cette nouvelle fonction, contredisant notre invariant qui stipule que le code est fixé1pendant l’exécution (seules les valeurs de la mémoire changent). Si sur ce même exemple, nous considérons maintenant comme code-objet l’association duλ-terme représentant le

code de la fonction et un environnement englobant les valeurs des variables libres de cette fonction, nous obtenons :

(λx.λy.(x y))(λz.z)

environnement vide → (λy.(x y))

avecx = (λz.z)

où ici, le code contient une variable libre dont la valeur est connue dans l’environnement (encore appelé substitution). Le code source d’un langage fonctionnel est bien leλ-calcul, car il exprime naturellement les

fonctions. Mais ce formalisme n’est pas suffisant pour exprimer les valeurs calculées par les programmes. Les fermetures et les substitutions remédient à ce défaut.

4.2.3 Correction d’une machine abstraite

Nous pouvons maintenant préciser la notion de correction d’une machine abstraite puisque nous savons, d’une part, dans quel langage exprimer les valeurs d’une machine et, d’autre part, quel est le calcul censé s’appliquer sur un programme.

Définition 20 (Décompilation [216]).

La décompilationD d’une machine abstraite est une fonction qui à un état de la machine associe un terme

du langage.

Le résulat d’un programme est l’image parD de l’état terminal de l’exécution de ce programme. Définition 21 (Correction d’une machine abstraite [216]).

Soit(E, →

E) une machine abstraite munie de ses fonctions de compilation C et de décompilation D. La

machine est dite correcte si pour tout codeC(e) qui a pour résultat s, alorsv est un réduit de e[•] (par

) :

SiC(e)→

E setv = D(s) alorse[•] v

Notons que nous ne préciserons pas qu’il existe un résultat de l’évaluation dee. L’évaluation peut être

infinie. Mais nous assurerons alors que si cette évaluation dee ne se termine pas, alors la machine abstraite

ne s’arrêtera pas pour donner un résultat incohérent.

Les bi-simulations établissent des équivalences entres des systèmes de relations, par exemple, entre deux systèmes de réécriture. [216] a utilisé des bi-simulations pour établir la correction de machines abstraites avec la proposition suivante.

Proposition 2 (Correction d’une machine abstraite)

Soit(E, →

E) une machine abstraite munie de ses fonctions de compilation C et de décompilation D. Si

la fonction de décompilation satisfait les conditions suivantes : 1. état initial : pour tout terme initiale, D(C(e)) = e[•] ;

2. état accessible : soientS1etS2deux états de la machine tels queS1

E S2. SiD(S1) est défini

alorsD(S2) est aussi défini aussi et, D(S1) D(S2) ; Si D(S1) = D(S2), nous parlons d’une

action silencieuse. Nous avons donc ces deux cas de figure :

S1 D E S2 D e1 + e2 S1 D E S2 D e1

3. progrès : il ne peut y avoir une infinité d’actions silencieuses ;

4. état terminal : siS est un état terminal et D(S) est défini, alors D(S) est soit une valeur soit D(S) \ (et D(S) n’est pas une valeur)

alors la machine abstraite est correcte. Preuve . Par induction sur

E (voir [216] pour les détails).

Le schéma qui suit, résume cette proposition dans un cas idéal :

S1 D E S2 D E · · · E Sn D terminal e C e[•] t2 · · · v

où chaque état de la machine abstraite correspond à un terme et chaque transition simule la réduction d’un et un seul radical.

Corollaire 1

SiS1est un état de la machine tel queD(S1) est défini et D(S1) e, alors il existe S2tel queD(S2) = e

etS1

E S2.

Preuve . Par application de la proposition 2 et du fait que soit confluent (voir [139] pour les détails).

Propriété 2 (Complétude d’une machine abstraite)

Soite une expression

1. sie[•] v alors C(e)

E S tel que D(S) = v

2. sie[•] · · · alors C(e)

E · · · (réductions infinies)

Preuve . Par induction sur et par application du corollaire 1.

4.3 Définition d’une BSP-CAM

4.3.1 Machine abstraite CAM

Pour commencer, nous allons tout d’abord décrire la machine séquentielle : la CAM (Categoritical Abstract

Machine) [76, 80]. La CAM était la machine abstraite de feu CAML2. Nous n’avons pas choisi la ZAM qui est la machine abstraite de OCaml pour des raisons de simplicité. En effet, cette machine est bien plus compliquée et les difficultés de simulation qu’elle entraîne sont hors propos dans ce travail.

La CAM utilise un jeu d’instructions qui est issu des combinateurs catégoriques [80]. Les instructions sont les suivantes :

Cst Op Fst Snd Push Swap Cons App Closure CloseRec Case

Le code est donc une liste de telles instructions terminée par l’«instruction vide» (fin de la liste). Les en-vironnements sont dénotés par des arbres binaires de valeurs (fermetures, constantes, opérateurs, vecteurs). Une fermeture est constituée d’un code et d’un environnement. La pile utilisée par cette machine est une

2CAML était l’ancêtre de Caml-light qui a ensuite donné OCaml. Le lecteur archéologue peut consulter

53 4.3. DÉFINITION D’UNE BSP-CAM {e ◦ P, Cst(c); C, t} → Cst {c ◦ P, C, t} (4.1) {e ◦ P, Op(op); C, t} → Op {[op, e] ◦ P, C, t} (4.2) {(e1, e2) ◦ P, Fst; C, t} → Fst {e1◦ P, C, t} (4.3) {(e1, e2) ◦ P, Snd; C, t} → Snd {e2◦ P, C, t} (4.4) {e ◦ P, Push; C, t} → Push {e ◦ e ◦ P, C, t} (4.5) {e1◦ e2◦ P, Swap; C, t} → Swap {e2◦ e1◦ P, C, t} (4.6) {e1◦ e2◦ P, Cons; C, t} → Cons {(e2, e1) ◦ P, C, t} (4.7) {([C, e1], e2) ◦ P, App; C, t} → App {(e1, e2) ◦ P, C; C, t} (4.8) {e ◦ P, Closure(C); C, t} → Closure {[C, e] ◦ P, C, t} (4.9) {e ◦ P, CloseRec(C); C, t} → CloseRec {[C, e] ◦ P, C; C, t} (4.10) {true ◦ P, Case(C1, C2); C, t} → Case {P, C1; C, t} (4.11) {false ◦ P, Case(C1, C2); C, t} → Case {P, C2; C, t} (4.12) {(n1, n2) ◦ P, Add; C, t} → Add {n1+ n2◦ P, C, t} (4.13) {e ◦ P, Isnc; C, t} → Isnc {e◦ P, C, t} avec  e= true si e = nc e= false sinon (4.14) {([e0, . . . , ei, . . . , en], i) ◦ P, Access; C, t} → Access {ei◦ P, C, t} (4.15) {(f, n) ◦ P, Init; C, t} → Init {(f, 0) ◦ · · · ◦ (f, n − 1) ◦ P, C, t} (4.16) {e0◦ · · · ◦ en−1◦ P, Tabn; C, t} → Tab {[e0, . . . , en−1] ◦ P, C, t} (4.17)

Figure 4.1 —Règles de transition de la CAM

liste d’environnements et non de valeurs. Une CAM est l’association du code courant, d’une pile et d’un type de contexte,loc ou glo (ces types seront utilisés pour différencier l’exécution en dehors ou en dedans

d’un vecteur). Nous avons donc :

Code ::= Instruction; Code | ε

Environnement ::= () | v | (Environnement, Environnement)

Fermeture ::= [Environnement, Code]

Pile ::= Environnement◦ Pile | •

CAM ::= {Pile, Code, Type}

Nous noterons la pileP , le code C et le type t.

Documents relatifs