# tar xf zyvaic.tar # mv squelette.ml zyvaic/trans.mlOn peut alors compiler le compilateur, après reconstruction des dépendances.
# cd zyvaic # make depend # make zyvaicLes moyens de mise au point à votre disposition sont, d'une part un imprimeur du code produit (option -pc de zyvaic), et d'autre part un simulateur qui exécute le code intermédiaire (option -ic de zyvaic et comportement par défaut).
type 'a procedure = Frame.frame * 'a type 'a program = { number_of_globals : int; main : 'a procedure; procedures : 'a procedure list } val program : Pp.program -> Code.stm program |
let rec cexpr env = function | Int i -> Const i | Bool b -> ... let rec cinstruction env = function | Sequence is -> Seq (cinstruction_list env is) | Print_int e -> Exp (Call (Frame.write_int, cexpr env e)) | ... and cinstruction_list env is = List.map (cinstruction env) is |
string * Pp.definition
en
une fonction Frame.frame * (Code.stm)procedure
.
Et finalement un programme Pp.program
en
(Code.stm)program
. Genre :
let cfun env f (s,{arguments = args ; result = r ; local_vars = locs ; body = ins}) = and new_env = env in (* Pour le moment *) f, Seq (cinstruction_list new_env ins) (* Note: les frames sont bidons, pour le moment *) let cprog {global_vars = g ; definitions = defs ; Pp.main = p} = let main_def = (* Corps du programme -> procédure *) ("main",{arguments = [] ; result = None ; local_vars = [] ; body = p}) in let globals = [] and frames = [] (* Pour le moment *) in let env_init = Env.create_global globals frames in let funs = List.map (fun def -> cfun env_init Frame.bidon def) defs in let principal = cfun env_init Frame.bidon main_def in { number_of_globals = List.length globals ; main = principal ; procedures = funs } |
cexpr
et cinstruction
,
dont les définitions sont plus ou moins données dans le
cours.
On ne se privera pas, pour des informations plus détaillées, de
consulter la section du poly sur la génération de
code (et de bien utiliser le module fourni
Gen pour les étiquettes et les temporaires.('a, 'b)environnement
des environnements qui associe des chaînes à
à des 'a
et à des 'b
.
Soit deux catégories de liaisons, la première pour les variables et la
seconde pour les fonctions.
On choisira donc
Frame.frame pour les fonctions et des valeurs du type
type acces = Tempo of Gen.temp | Memo of Code.expr |
read
(qui prend un nom de variable en argument) pose un
léger problème.# tar xf zyvaic.tar # mv trans.ml zyvaic # cd zyvaic # make depend # make zyvaic
program var n : integer; function fib (n : integer) : integer; begin if n <= 1 then fib := 1 else fib := fib (n-1) + fib(n-2) end ; begin read (n); writeln (fib (n)) end. |
*** Code généré *** function fib args = $t106 result = $t105 Seq Seq Cjump L12 L13 (<= $t106 1) L13: (set $t105 (+ (call fib (- $t106 1)) (call fib (- $t106 2)))) Jump L14 L12: (set $t105 1) L14: *** Code canonique et linéarisé *** (set $t108 $t106) (set $t107 1) Cjump L12 L13 (<= $t108 $t107) L13: (set $t115 $t106) (set $t114 1) (set $t116 (- $t115 $t114)) (set $t117 (call fib $t116)) (set $t118 $t117) (set $t110 $t106) (set $t109 2) (set $t111 (- $t110 $t109)) (set $t112 (call fib $t111)) (set $t113 $t112) (set $t105 (+ $t118 $t113)) Jump L14 L12: (set $t105 1) L14:Le code « canonique et linéarisé » est très mauvais. On remarque en particulier l'utilisation clairement excessive des temporaires dès qu'il faut calculer un peu, vers l'étiquette L13. En fait on préférerait un code de ce genre, également canonique (pas d'appels de fonction dans les expressions, ordre gauche-droite respecté) :
*** Code canonique et linéarisé *** Cjump L12 L13 (<= $t106 1) L13: (set $t110 (call fib (- $t106 1))) (set $t109 (call fib (- $t106 2))) (set $t107 (+ $t110 $t109)) Jump L14 L12: (set $t105 1) L14:
rewrite_exp
et rewrite_stm
ne sont pas en cause,
elle réalisent simplement les réécritures définies dans le
poly.
(* test de commutation vraiment simple *) let commute c s = false |
lus_dans_exp
de type
Code.exp -> Gen.temp list
qui renvoie la liste des
temporaires lus par une expression.
écrits_dans_stms
de type
Code.stm list -> Gen.temp list
qui renvoie la liste des
temporaires écrits par un bout de code.
lus_dans_exp c
et écrits_dans_stms s
sont disjoints, alors le code s et l'expression c commutent.
*** Code canonique et linéarisé *** fib: Cjump L12 L13 (<= $t106 1) L13: (set $t110 (call fib (- $t106 1))) (set $t109 (call fib (- $t106 2))) (set $t107 (+ $t110 $t109)) Jump L14 L12: (set $t105 1) L14: (jump fib_end)Le code commence par une étiquette
fib
et se
termine par un saut vers l'étiquette fib_end
qui sont
ajoutés ci-dessus. C'est normalement implicite, mais j'ai explicité.fib
et peut se terminer par
un transfert du contrôle vers L12
ou L13
Le bloc d'étiquette L12
se termine nécessairement par un
transfert du contrôle au bloc L14
, etc.
Les aventures du contrôle au cours des exécution possibles d'un programme sont
avantageusement exprimées par un graphe de flot dont les sommets
sont les blocs de base et les arcs traduisent les transferts
potentiels de la fin d'un bloc au début d'un autre.
Voici le graphe de flot du code de fib.
L14
est vide, c'est à dire qu'il ne contient
pas d'intruction et se termine par un saut inconditionnel.
Cette condition est indiquée par un sommet grisé dans le graphe de flot.
L'analyse du graphe de flot permet par exemple l'élimination
systématique des blocs vides (c'est à dire si on y pense des sauts
vers des sauts inconditionels).opt_trace
(de Caml, vers la ligne 100 de linear.ml)
qui fait ce petit travail :
(* Optimisation du contrôle de f (dont le code est c) *) let opt_trace f c = let blocks = code_to_blocks f c in if !Misc.dump_flowgraph then print_flow_graph blocks; peephole f (blocks_to_code blocks) (* Idem, mais inoffensif *) let opt_trace f c = peephole f c |
opt_trace
inoffensive,
afin d'activer celle qui travaille vraiment.
On recompile (make zyvaic) on essaie (./zyvaic -pc
test/fib.p). Ça plante, c'est normal, car il y a deux fonctions à
écrire. Enfin, nous y voilà !
Donc, il faut écrire code_to_blocks
et
blocks_to_code
(chercher les raise PasFini
).
Les types sont les suivants :
val code_to_blocks : Frame.frame -> Code.stm list -> basic_block list val blocks_to_code : basic_block list -> Code.stm list |
basic_bloc
des blocs de base mérite une petite
explication.
type basic_block = {enter:Gen.label ; mutable succ:stm ; body:stm list} |
Frame.frame_name f
et les sauts vers l'étiquette Frame.frame_return f
représentent la fin de l'exécution du corps de la fonction.code_to_blocks
.
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...Label lab
. Alors le bloc
en cours de construction est terminé (son champ succ
est
Jump lab
). Un nouveau bloc étiqueté lab
commence.
Frame.frame_return f
.
blocks_to_code
, assez facile au fond
(mettre le code des blocs bout-à-bout).
code_to_blocks
est suffisament avancée, visualiser les graphes
de flot.
# ./zyvaic -pg test/fib.p | dot -Tps | kghostview -Muni de sa nouvelle option -pg, zyvaic envoie une représentation du graphe de flot sur sa sortie standard. La construction de tuyau (pipe, noté «
|
») connecte
cette sortie à l'entrée standard de la commande suivant dot qui ici
(option -Tps) transforme la représentation du graphe en un
dessin en Postscript, visualisable par kghostview après un second
tuyau. Ouf !# ./show_flow_graph test/fib.pLe défi est de produire, par
./show_flow_graph test/quick.p
, le
magnifique graphe de flot suivant.
Ce document a été traduit de LATEX par HEVEA.