# 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.