let rec cexpr env = function | Int i -> Const i | Bool b -> Const (if b then 1 else 0) | Pp.Bin (op,e1,e2) -> Bin (cbinop op, cexpr env e1, cexpr env e2) | _ -> raise PasFini |
cbinop
etait donné dans le squelette).
Sinon, du point de vue de Caml, on notera une intéressante
utilisation
de l'expression if ... then ... else...
.
| Bool b -> Const (if b then 1 else 0) |
| Bool b -> if b then Const 1 else Const 0 |
| Bool true -> Const 1 | Bool false -> Const 0 |
cinstruction_list
pour la séquence, ccond
pour la
conditionelle etc.).
À part ça, pas grand chose à dire en fait.
let rec cinstruction env i = match i with | Sequence is -> Seq (cinstruction_list env is) | If (e,inst,insf) -> begin match e with Pp.Bin (op,e1,e2) when is_relop op -> ccond env op e1 e2 inst insf | _ -> ccond env Pp.Ne e (Bool false) inst insf end | While (e,i) -> begin match e with Pp.Bin (op,e1,e2) when is_relop op -> cwhile env op e1 e2 i | _ -> cwhile env Pp.Ne e (Bool false) i end | Write_int e -> Exp (Call (Frame.write_int, [cexpr env e])) | Writeln_int e -> Exp (Call (Frame.writeln_int, [cexpr env e])) | _ -> raise PasFini and cinstruction_list env is = List.map (cinstruction env) is (* compilation de la conditionnelle ``If'' *) and ccond env op e1 e2 inst insf = let lt = Gen.new_label () and lf = Gen.new_label () and lend = Gen.new_label () in Seq [Cjump (crelop op,cexpr env e1, cexpr env e2,lt,lf) ; Label lt ; cinstruction env inst ; Jump lend ; Label lf ; cinstruction env insf ; Label lend] (* compilation de la boucle ``While'' *) and cwhile env op e1 e2 i = let enter = Gen.new_label () and test = Gen.new_label () and lout = Gen.new_label () in Seq [Jump test ; Label enter ; cinstruction env i ; Label test ; Cjump (crelop op,cexpr env e1,cexpr env e2,enter,lout) ; Label lout] |
let rec cinstruction env i = match i with | Sequence is -> Seq (cinstruction_list env is) … and cinstructions env is = List.map (cinstruction env) is |
cinstruction
env
est une application partielle (i.e.
on ne donne pas tous les arguments de cinstruction
).
Le type de cinstruction
est en effet le suivant
val cinstruction : 'a -> Pp.instruction -> Code.stm |
env
ne sert à rien pour le moment, son type est
assez indéterminé).
Le type de l'application cinstruction
env
est donc
Pp.instruction -> Code.stm
.
C'est à dire que cinstruction
qu'il est commode de voir comme
une fonction prenant deux arguments est en réalité une fonction qui
rend une fonction, dont le type se lit en fait :
val cinstruction : 'a -> ( Pp.instruction -> Code.stm ) |
->
» penche à droite !)
and cinstructions env is = match is with | [] -> [] | i::rem -> cinstruction env i::cinstruction_list env rem |
type access = Local of Gen.temp | Address of Code.exp |
let rec let rec cexpr env = function … | Get s -> begin match Env.find_var env s with | Local t -> Temp t | Address a -> Mem a end … |
Mem
des expressions du code intermédiaire).
let rec cinstruction env i = match i with | Set (s,e) -> let ec = cexpr env e in begin match Env.find_var env s with | Local t -> Move_temp (r,ec) | Address a -> Move_mem (a,ec) end … |
x = *q ; *p = *q ; |
x
et p
sont des temporaires puis des cases
mémoire.
let cfun env (s,{arguments = args ; result = r ; local_vars = locs ; body = is}) = let f =Frame.bidon in let new_env = env in f,Seq (cinstruction_list new_env is) |
let cfun env (funname,{arguments = args ; result = r ; local_vars = locs ; body = ins}) = let f = Env.find_definition env s in |
let locals = List.map (fun (s,_) -> (s, Local (Gen.new_temp ()))) locs |
locs
est extrait de la représentation sous forme de
syntaxe abstraite de la fonction compilée, c'est une liste
de paires : variable × type).
and params = List.map2 (fun (s,_) t -> (s, Local t)) args (Frame.frame_args f) |
and result = match Frame.frame_result f with | Some t -> [funname,Local t] | None -> [] in |
let new_env = Env.change_local_vars env (result @ params @ locals) in Seq (cinstruction_list new_env ins) |
cfun
.
Mais comment fabriquer cet environnement initial, c'est à dire comment
named_frame
).
Avec un peu d'embalage, il vient :
let make_frame (funname,{arguments = args ; result = r }) = funname,Frame.named_frame funname args r |
let make_global i = Address (match i with | 0 -> Temp Frame.global_register | _ -> Bin (Uplus, Temp Frame.global_register, Const (i*Frame.wordsize))) |
let make_globals vs = let rec map_rec n = function | [] -> [] | (x,_)::xs -> (x, make_global n x) :: map_rec (n+1) xs in map_rec 0 vs |
let cprog {global_vars = g ; definitions = defs ; Pp.main = p} = let globals = make_globals g in let main_def = ("main",{arguments = [] ; result = None ; local_vars = [] ; body = p}) in let env_init = Env.create_global globals (List.map make_frame (main_def :: defs)) in let funs = List.map (fun def -> cfun env_init def) defs in let principal = cfun env_init main_def in { number_of_globals = List.length globals ; main = principal ; procedures = funs } |
Seq
) dans le code
produit.
| Function_call (f,args) -> Call (Env.find_definition env f,List.map (cexpr env) args) |
| Sequence is -> List.fold_right (cinstruction env) is r |
cinstruction
(et du placement de « env » en
première position).let memory = Gen.new_temp () |
let rec lus_dans_exp = function | Const _ | Name _ -> [] | Bin (_, e1, e2) -> lus_dans_exp e1 @ lus_dans_exp e2 | Mem e -> memory :: lus_dans_exp e | Temp t -> [t] (* Cas impossible *) | Call (_, _) -> assert false |
assert
false
) ne doit pas se
présenter si l'expression passée en argument à lus_dans_exp
est
une expression résiduelle, notée c dans les règles de canonisation.Move_temp
.
En effet, comme montré dans les transparents du cours
(ici et là), le test de commutation
ne s'applique qu'à une suite d'instructions issues de la canonisation
des expressions. En outre,
la canonisation des expressions ne produit que des
Move_temp
.
Et en outre encore, les appels de fonction ne peuvent apparaître qu'au sommet
de l'expression e
dans l'instruction Move_temp (t, e)
.
let rec écrits_dans_stm = function | [] -> [] | h :: rest -> match h with | Move_temp (t, Call (_,_)) -> t :: memory :: écrits_dans_stm rest | Move_temp (t, _) -> t :: écrits_dans_stm rest (* Cas impossibles *) | _ -> assert false |
let rec commute e stm = let r = lus_dans_exp e and w = écrits_dans_stm stm in List.for_all (fun x -> not (List.mem x r)) w |
écrits_dans_stm
(en anticipant des changements de la nature des expressions,
par exemple l'ajout au langage source d'une expression if
...
then
... else
...).
Une hypothèse minimale et certainement toujours vérifiée est que
le code passé en argument est canonique.
Le code devient alors celui ci.
exception Universe let écrits_une h = match h with | Move_temp (t, Call (_,_)) -> [memory ; t] | Move_temp (t, _) -> [t] | Exp (Call (_,_)) -> [memory] | Exp _ -> [] | Move_mem (_,_) -> [memory] | Label _ -> [] | Jump _|Cjump (_,_,_,_,_) -> raise Universe | Seq _ -> assert false let rec écrits_dans_stm = function | [] -> [] | h :: rest -> écrits_une h @ écrits_dans_stm rest |
Universe
signifie que le code passé en
argument peut écrire partout. C'est une approximation par excès
traduisant que nous ne ne cherchons pas à savoir où les sauts peuvent
conduire.
Nous utilisons cette exception car nous n'avons pas de moyen de
représenter tous les temporaires
possibles comme une liste de temporaires.
(On aurait aussi pu modifier le type des ensembles de temporaires,
pour y inclure l'ensemble de tous les temporaires).commute
. Et si elle est levée lors de l'examen du code
stm
, alors stm
et e
ne commutent pas.
let rec commute e stm = try let r = lus_dans_exp e and w = écrits_dans_stm stm in List.for_all (fun x -> not (List.mem x r)) w with Universe -> false |
écrits_dans_stm
, alors on
on est justement en train de calculer les temporaires écrits par le
code et le saut n'y change rien.
let écrits_une labs h = match h with … | Jump l -> if List.mem l labs then [] else raise Universe | Cjump (_,_,_,l1,l2) -> if List.mem l1 labs && List.mem l2 labs then [] else raise Universe … let rec get_labels = function | [] -> [] | Label l::rem -> l::get_labels rem | _::rem -> get_labels rem let écrits_dans_stm stms = let labs = get_labels stms in let rec do_rec = function | [] -> [] | h :: rest -> écrits_une labs h @ do_rec rest in do_rec stms |
Un fois amorcée la création d'un bloc, on cherche sa fin en un parcours de la liste des instructions, si on trouve...C'est une description assez précise de la fonction
- La définition d'une étiquette
Label lab
. Alors le bloc en cours de construction est terminé (son champsucc
estJump lab
). Un nouveau bloc étiquetélab
commence.- Un saut quel qu'il soit, le bloc en cours se termine par ce saut. L'instruction qui suit est forcément une étiquette commençant un nouveau bloc.
- Plus rien (fin de la liste des instructions). Le bloc en cours se termine par un saut vers
Frame.frame_return f
.- N'importe quelle autre instruction, alors cette instruction est à ajouter en fin de bloc et il faut continuer à chercher la fin du bloc en cours.
in_block
locale à la fonction cut_blocks
dont la mission, abondamment
commentée, est de produire une liste de bloc à partir d'un code, deux
étiquettes initiale et finale étant données.
let cut_blocks start_label exit_label code = let rec in_block cur_lab cur_ydob = function (* Cas 1. *) | Label lab::rem -> let r = in_block lab [] rem in {enter=cur_lab ; succ=Jump lab ; body=List.rev cur_ydob}::r (* Cas 2. *) | (Jump _ | Cjump (_,_,_,_,_)) as stm::rem -> let r = start_block rem in {enter=cur_lab ; succ=stm ; body=List.rev cur_ydob}::r (* Cas 4. *) | stm::rem -> in_block cur_lab (stm::cur_ydob) rem (* Cas 3. *) | [] -> (* dernier bloc, ajsuccer un saut vers end_label *) [{enter=cur_lab ; succ=Jump exit_label ; body=List.rev cur_ydob}] |
cur_ydob
(ydob à l'envers c'est body).start_block
rem
(cas 2.), la fonction
start_block
est chargée de commencer un nouveau bloc.
(alors que la mission de in_block
est de trouver la fin d'un
bloc).
and start_block stms = match stms with | Label lab::rem -> in_block lab [] rem (* on pourrait aussi chercher l'étiquette suivante *) | _ -> assert false in |
cut_blocks
par un appel
initial à in_block
(et non à start_block
car l'étiquette
premier bloc, connue, n'est pas présente dans le code).
in_block start_label [] code |
blocks_to_code
la pose est à l'inverse de la
dépose, il faut donc supprimer l'étiquette du premier bloc, mais pas
les autres, voir le poly.
On devrait parallèlement supprimer un éventuel saut final vers l'épilogue
dans le dernier bloc. Mais, si il y eu des optimisations, c'est un peu
plus compliqué. En fait, on supprime les sauts inutiles
vers l'épilogue lors de la rectification des sauts
bi-étiquettes.
Plus précisément c'est seulement plus tard que
l'on se préoccupe d'éviter
un label parasite derrière un saut conditionel final dont une des
cibles est l'épilogue.
Alors, tant qu'à faire, on supprimera le saut final inconditionnel vers
l'épilogue au même moment.Ce document a été traduit de LATEX par HEVEA.