Précédent Index Suivant

6.5   Compilation du l-calcul

Cette section décrit une machine à environnement pour l'évaluation paresseuse, appelée machine de Krivinne, qui est le modèle le plus simple pour la compilation du l-calcul.

6.5.1   Évaluateur de l-calcul

Le schéma d'évaluation d'un l-terme est le suivant :

env terme pile env terme pile
e c S e c S
e MN S e M (N.e::S)
er l x.M s::S (e,s)r,x M S
(e,(M.e2))r,x x S e2r M S
(e,a)r,y x S er x S


où l'environnement contient des couples (termes,environnement), c'est à dire des valeurs calculables.

Le type Caml suivant correspond aux termes ou expressions de ce l-calcul étendu : type expr =
* Kapp of expr * expr
* Kid of string
* Klambda of string * expr
* ;;
* Type expr defined.

6.5.2   Instructions de la machine

La machine suivante (dérivée de la machine de Krivinne) comporte 3 instructions la compilation du l-calcul et un étiquetage.

Les types Caml suivants : type instr =
* Grab (*met le sommet de la pile dans l'environnement *)
* Push of int (* *)
* Access of int (*acces à la n-ieme variable de l'environnement *)
* Label of int (*etiquetage d'une partie de code *)
* ;;
* Type instr defined. type instrI =
* GrabI (*met le sommet de la pile dans l'environnement *)
* PushI of int (* *)
* AccessI of int (*acces à la n-ieme variable de l'environnement *)
* ;;
* Type instrI defined.

correspondent respectivement aux instructions de l'assembleur et aux instructions machine (après assemblage).

6.5.3   Schéma de compilation

On note [M]r la compilation du terme M dans l'environnement r.

Les deux fonction new_label et reset_label manipulent le compteur des nouveaux numéros de label. let new_label,reset_label =
* let c = ref 0 in
* ( function () ® (incr c)!c),(function () ® c:=0!c)
* ;;
* reset_label : unit ® int = áfunñ
* new_label : unit ® int = áfunñ

La variable globale aCompiler contient des morceaux de code à compiler par la suite. let aCompiler = ref ([  ]:instr list);;
* aCompiler : instr list ref = ref [  ]

Les deux fonctions principales de ce compilateur sont mk_lab pour la création et le placement de nouveaux labels et la fonction compiler qui traduit les l-termes en instructions machine. let rec mk_lab env term = let lab = new_label() in
* (aCompiler := ( Label lab)::((compiler env term)@(!aCompiler))
* lab)
* and
* compiler env = function
* Kid v ® [  Access ((index v env)+1)  ]
* Klambda (v,e) ® Grab::(compiler (v::env) e)
* Kapp (f,a) ® let u = (mk_lab env a) in
* Push u :: (compiler env f)
* ;;
* mk_lab : string list ® expr ® int = áfunñ
* compiler : string list ® expr ® instr list = áfunñ

Enfin la fonction cc initialise les variables globales de compilation et compile un terme à partir d'un environnement vide.

let cc e = reset_label()
* aCompiler :=[  ]
* let sub_code = (compiler [  ] e)
* in sub_code @ (!aCompiler)
* ;;
* cc : expr ® instr list = áfunñ

6.5.4   Schéma d'évaluation

Une fois les l-termes traduits en instructions machine, il est nécessaire d'avoir un interprète de cette machine virtuelle. Voici le schéma d'évaluation des instructions de cette machine :
env code pile env code pile
e Push l;C S e C (l.e)::S
e Grab; C s::S (e,s) C S
e (*)Grab;C () arrêt sur (e,*)
(e,(l.e2)) Acc 0;C;Label l; C2 S e2 C2 S


Un des points importants est le critère d'arrêt. L'instruction Grab correspond à une abstraction. S'il n'y a pas d'argument sur la pile, cette abstraction ne trouve donc pas d'argument et le calcul s'arrête. Il n'y a pas de réduction sous le l.

Le programme Caml suivant effectue une passe d'assemblage des instructions de type instr et évalue le résultat, une liste d'instructions instrI.

Pour faire disparaître les labels, il est nécessaire de connaître la longueur de chaque instruction :

let longueur_code = function
* Grab ® 1
* Push ® 1
* Access ® 1
* Label ® 0
* ;;
* longueur_code : instr ® int = áfunñ

La fonction pass_one associe à chaque label l'adresse de l'instruction étiquetée.

let pass_one =
* let rec pass_rec pc = function
* [  ] ® [  ]
* Label n :: rest ® (n,pc):: pass_rec pc rest
* instr :: rest ® pass_rec (pc+(longueur_code instr)) rest
* in pass_rec 1
* ;;
* pass_one : instr list ® (int * int) list = áfunñ

La fonction assemble traduit du code assembleur (intsr list) en code machine (instrI list).

let assemble code =
* let list_label = pass_one code
* in
* let rec asm = function
* [  ] ® [  ]
* Label n :: rest ® asm rest
* Push l:: rest ® PushI (assoc l list_label) :: asm rest
* Grab :: rest ® GrabI :: asm rest
* Access n :: rest ® AccessI n :: asm rest
* in
* asm code
* ;;
* assemble : instr list ® instrI list = áfunñ

type closure = C of int * (closure list) ref
* ;;
* Type closure defined. let rec nth l a = match l with
* [  ] ® failwith ``nth''
* h::t ® if a=1 then h else nth t (a-1)
* ;;
* nth : a list ® int ® a = áfunñ

La fonction interprete exécute le code machine passé comme argument et retourne une fermeture (closure).

let interprete code =
* let rec interp env pc stack = match (nth code pc) with
* AccessI n ® (try
* let u = nth env n
* in
* match u with
* (C (n,e)) ® interp !e n stack
* with
* x ® (C (pc, ref env)))
* PushI n ® interp env (pc+1) ((C (n,ref env))::stack)
* GrabI ® (match stack with
* [  ] ® (C (pc,ref env))
* so::S ® interp (so::env) (pc+1) S)
* in
* interp [  ] 1 [  ]
* ;;
* interprete : instrI list ® closure = áfunñ

Enfin la fonction Kall interprète le code compilé et assemblé d'un l-terme. let Kall exp = interprete (assemble (cc exp))
* ;;
* Kall : expr ® closure = áfunñ

6.5.5   Exemples

let EX1 =
* (Kapp
* ((Kapp
* ((Klambda (``f'',(Klambda (``x'',(Kapp ((Kid ``f''),(Kid ``x''))))))),
* (Klambda (``x'',(Kid ``x''))))),
* (Klambda (``x'',(Kid ``x'')))))
* ;;
* EX1 : expr = Kapp (Kapp (Klambda (``f'', Klambda (``x'', Kapp (Kid ``f'', Kid ``x''))),
* Klambda (``x'', Kid ``x'')), Klambda (``x'', Kid ``x'')) cc EX1;;
* - : instr list = [ Push 1 Push 2 Grab Grab Push 3 Access 2 Label 3 Access 1
* Label 2 Grab Access 1 Label 1 Grab Access 1 ] assemble (cc EX1);;
* - : instrI list = [ PushI 10 PushI 8 GrabI GrabI PushI 7 AccessI 2
* AccessI 1 GrabI AccessI 1 GrabI AccessI 1 ] interprete (assemble(cc EX1));;
* - : closure = C (10, ref [  ])

6.5.6   Exercices

  1. Ajouter un mode debug pour la fonction interprète qui trace l'état de la pile, de l'environnement et du compteur ordinal.
  2. Étendre le l-calcul traité par le compilateur aux déclarations locales let in et let rec in. Décrire l'évaluation de ces nouveaux termes, les instructions machines correspondantes, leur schéma de compilation et d'évaluation, et le nouveau compilateur et interprète associés.

6.5.7   Bibliographie


Précédent Index Suivant