# tar xf zyval.tar # cp squelette.ml zyval/liveness.ml # cd zyval # make depend # make
val flow : instr list -> flowinstr list val interference : flowinstr list -> igraph |
flowinstr
nous y reviendrons).flow
.
let flow code = let g = mk_graph code in let niters = fixpoint g in if !verbose then begin prerr_endline ("Graphe de flot calculé en "^string_of_int niters^" itérations") end ; (* C'est le résultat *) graph_to_flowinstrs g |
mk_graph
, notons tout de
même que nous avons déjà calculé un graphe de flot à l'occasion de
l'optimisation du contrôle) et
la transformation du graphe en liste d'instructions décorées par
les informations de liveness (c'est la
fonction graph_to_flow_instr
).fixpoint
qui calcule
les Out et les In.
Son type est le suivant :
val fixpoint : val fixpoint : flowinfo Graph.t -> int |
'a Graph.t
) dont les sommets sont décorés par
diverse informations pertinentes (type flowinfo
).
type flowinfo = { instr : Ass.instr ; (* Une instruction *) (* Comportement de l'instruction *) def : temp set; (* Écrits par l'instruction Def *) use : temp set; (* Lus par l'instruction Use *) (* Temporaires vivants *) mutable live_in : temp set; (* En entrée In *) mutable live_out : temp set; (* À la sortie Out *) } |
fixpoint
sont déjà décorés par toutes les informations non mutables
(c'est à dire instr
, def
, use
). La mission
de la fonction fixpoint
est de calculer les informations
mutables (c'est à dire live_in
et live_out
) ou autrement
dit les ensembles de temporaires vivants en entrée et en sortie de
l'instruction considérée.
On notera que les ensembles de temporaires sont réalisés à l'aide du
module Smallset.fixpoint
qui ne fait
rien). On a :
# ./zyval -4 factrec.p *** Graphe de flot *** fact: # <= # subu $sp, $sp, fact_f # <= # move $113, $ra # $113 <= $ra # move $112, $s0 # $112 <= $s0 # move $108, $a0 # $108 <= $a0 # li $114, 1 # $114 <= # ble $108, $114, L12 # <= $108 $114 # L13: # <= # sub $a0, $108, 1 # $a0 <= $108 # jal fact # $v0 $a0 $ra <= $a0 # move $109, $v0 # $109 <= $v0 # mul $107, $108, $109 # $107 <= $108 $109 # b fact_end # <= # L12: # <= # li $107, 1 # $107 <= # fact_end: # <= # move $v0, $107 # $v0 <= $107 # move $ra, $113 # $ra <= $113 # move $s0, $112 # $s0 <= $112 # addu $sp, $sp, fact_f # <= # j $ra # <= $v0 $s0 $ra #Chaque instruction est suivie des temporaires définis et utilisés (sous la forme # Def<= Use, remarquez au passages les Def et les Use de l'appel de fonction et de l'instruction de retour). Ensuite viennent les Out, ici vides.
fixpoint
on obtient normalement :
# ./zyval -4 factrec.p *** Graphe de flot *** fact: # <= # $a0 $s0 $ra subu $sp, $sp, fact_f # <= # $a0 $s0 $ra move $113, $ra # $113 <= $ra # $a0 $s0 $113 move $112, $s0 # $112 <= $s0 # $a0 $112 $113 move $108, $a0 # $108 <= $a0 # $108 $112 $113 li $114, 1 # $114 <= # $108 $112 $113 $114 ble $108, $114, L12 # <= $108 $114 # $108 $112 $113 L13: # <= # $108 $112 $113 sub $a0, $108, 1 # $a0 <= $108 # $a0 $108 $112 $113 jal fact # $v0 $a0 $ra <= $a0 # $v0 $108 $112 $113 move $109, $v0 # $109 <= $v0 # $108 $109 $112 $113 mul $107, $108, $109 # $107 <= $108 $109 # $107 $112 $113 b fact_end # <= # $107 $112 $113 L12: # <= # $112 $113 li $107, 1 # $107 <= # $107 $112 $113 fact_end: # <= # $107 $112 $113 move $v0, $107 # $v0 <= $107 # $v0 $112 $113 move $ra, $113 # $ra <= $113 # $v0 $ra $112 move $s0, $112 # $s0 <= $112 # $v0 $s0 $ra addu $sp, $sp, fact_f # <= # $v0 $s0 $ra j $ra # <= $v0 $s0 $ra #En regardant bien on note que les temporaires de sauvegardes des callee-saves, 112 et 113 sont vivants à travers toute la fonction (mais pas à la sortie, à cause des Use de l'instruction de retour). On note aussi que la durée de vie de temporaires « auxiliaires » (par exemple 114) est bien courte. Enfin, la fonction lit bien son argument dans a0, comme il est visible en considérant les Out de son étiquette d'entrée.
# ./zyval -4 factrec.p > a # diff a factrec-live.txtOu mieux encore :
# ./zyval -4 factrec.p | diff - factrec-live.txtIl ne doit rien se passer.
# ./zyval -4 factrec.p # dot -Tps factrec-fact.ig > fact.ps # dot -Tps factrec-main.ig > main.ps # kghostview fact.ps & kghostview main.ps & #On a ces deux jolis dessins :
fixpoint
. On notera que cette
fonction renvoie un entier qui est le nombre d'itérations effectuées.
Voici un rappel de quelques informations utiles.
|
interference
du squelette calcule déja le graphe des
interférences.
L'exercice consiste à ajouter dans ce graphe les arcs « moves » qui
indiquent des relation d'affinités entre temporaires.
Par contraste, les arcs d'interférence indiquent une relation
d'incompatibilité.
Au sens que l'on aimerait bien que deux temporaires reliés par un arc
« move » occupent au final le même registre, tandis que deux
temporaires reliés par un arc « d'interférence » ne peuvent pas
occuper au final le même registre.interference
son argument et son
résultat :
val interference : flowinstr list -> igraph |
type flowinstr = {finstr:Ass.instr ; fdef: Gen.temp set ; fuse : Gen.temp set ; fout : Gen.temp set} |
flowinstr
est
facilement produite à partir du graphe de flot complet de la question
précédent simplement (par la fonction graph_to_flowinstrs
) en
oubliant les informations devenues inutiles, c'est à dire la structure
même du contrôle et les In.igraph
) est un graphe non-orienté
(type générique ('a, 'b) Sgraph.t
) dont les sommets
(type interference
) sont des temporaires et les arcs
(type ilab
sont de deux catégories : arcs d'interférence et
d'affinité).
(* Sommets du graphe d'interférence *) type interference = { temp : temp; (* Les champs suivants sont utiles pour l'allocation de registres *) mutable color : temp option ; mutable occurs : int ; mutable degree : int ; mutable elem : ((interference Sgraph.node) Partition.elem) option ; } (* Les deux sortes d'arcs *) type ilab = Inter | Move_related type igraph = (interference, ilab) Sgraph.t |
temp
des sommets.interference
le graphe construit est dans
la variable ig
et sera construit par des appels aux fonctions
du module Sgraph des graphes
non-orientés.
Le graphe est créé vide initialement :
let interference code = let ig = Sgraph.create ibidon in … |
(* Besoin d'une association temporaire -> sommet *) let temp2nodes = Hashtbl.create 17 in let get_node r = try Hashtbl.find temp2nodes r with | Not_found -> let n = Sgraph.new_node ig (mk_info r) in Hashtbl.add temp2nodes r n ; n in … |
k
qui peut
être Inter
ou Move_related
) entre les deux
sommets. Aucun arc entre registres n'est fabriqué, car l'allocation de
registres se passe de cette information sans intérêt (deux registres de
toute façon interfèrent toujours).
let mk_edge n1 n2 k = let r1 = (Sgraph.info ig n1).temp and r2 = (Sgraph.info ig n2).temp in if not (mem r1 registers) || not (mem r2 registers) then Sgraph.new_edge ig n1 n2 k in … |
i
, on peut maintenant
construire les sommets et les arcs d'interférence par elle
enegendrés :
(* Fabrication du graphe d'interférence proprement dit *) let step_instr i = match i.finstr with | Move (_,_,_) -> Smallset.iter i.fdef (fun d -> let nd = get_node d in Smallset.iter (Smallset.diff i.fout i.fuse) (fun r -> mk_edge (get_node r) nd Inter)) | _ -> Smallset.iter i.fdef (fun d -> let nd = get_node d in Smallset.iter i.fout (fun r -> mk_edge nd (get_node r) Inter)) in |
iter
ci dessus est l'itération sur
tous les éléments d'un ensemble de temporaires (module Smallset).step_instr
à toutes les
instructions du code passé en argument. Pour des raisons esthétiques
on s'assure aussi que tous les registres de la machine sont bien mis
dans le graphe.
(* Un sommet par registre machine *) Smallset.iter registers (fun r -> ignore (get_node r)) ; (* Pour toutes les instructions... *) List.iter step_instr code ; |
Move_related
) au graphe ig
. C'est à dire :
Move_related
.
Move_related
sont indiqués en pointillés dans les dessins).# ./zyval -4 factiter.p # dot -Tps factiter-fact.ig > fact.ps # kghostview fact.psdonnera ce joli dessin.
Ce document a été traduit de LATEX par HEVEA.