Skip to content
Snippets Groups Projects
regalloc.ml 12.10 KiB
open Batteries
open Prog
open Linear
open Rtl
open Linear_liveness
open Utils
open Report
open Options



(* Allocation de registres *)

(* Nous allons procéder à l'allocation de registres, par coloration de graphe
   d'interférences.

   Le but de l'allocateur est d'associer à chaque pseudo-registre utilisé dans
   une fonction Linear, un emplacement (type [loc]). *)

type loc = Reg of int | Stk of int

(* Un emplacement (location en anglais) est soit un registre machine (identifié
   par son numéro [r] entre 0 et 31 inclus) : [Reg r], soit un emplacement sur
   la pile [Stk o] signifiant un décalage de [o] octets par rapport au pointeur
   de trame présent dans le registre [s0] (aussi appelé [fp] pour frame
   pointer). *)

(* Nous vous fournissons, ci-dessous, une implémentation naïve qui évince tous
   les pseudo-registres sur la pile. *)

let regs_in_instr i =
  Set.union (gen_live i) (kill_live i)

let regs_in_instr_list (l: rtl_instr list) : reg Set.t =
  List.fold_left
    (fun acc i -> Set.union acc (regs_in_instr i))
    Set.empty l

let regalloc_on_stack_fun (f: linear_fun) : ((reg, loc) Hashtbl.t * int)=
  let allocation = Hashtbl.create 10 in
  let regs = regs_in_instr_list f.linearfunbody in
  let regs = Set.diff regs (Set.of_list f.linearfunargs) in
  let next_stack_slot =
    List.fold_left (fun next_stack_slot r ->
        Hashtbl.replace allocation r (Stk (next_stack_slot));
        next_stack_slot - 1
      ) (-1) (Set.to_list regs) in
  (allocation, next_stack_slot)


(* Nous allons maintenant construire un graphe d'interférence de registres
   (register interference graph, ou rig). Le type d'un rig est donné par le type
   OCaml [(reg, reg Set.t) Hashtbl.t], i.e. une table dont les clés sont des
   registres et les valeurs sont des ensembles de registres qui "interfèrent"
   avec le registre-clé. Cela correspond à la relation d'adjacence dans le
   graphe d'interférence. *)

(* La fonction [add_to_interf rig x y] ajoute [y] à la liste des registres qui
   interfèrent avec [x] dans le graphe [rig].

   On pourra utiliser la fonction [Hashtbl.modify_def] qui permet de modifier la
   valeur associée à une clé.

   Par exemple, l'appel [Hashtbl.modify_def def k f rig] modifie la valeur
   associée à la clé [k] dans le graphe [rig].

   [f] est une fonction qui prend en entrée l'ancienne valeur, et qui retourne
   la nouvelle valeur (type ['b -> 'b], si [rig] est de type [('a,'b)
   Hashtbl.t], i.e. ['b] est le type des valeurs).
   [def] est la valeur par défaut donnée à [f] s'il n'existe pas d'ancienne
   valeur pour la clé [k].

   Attention, les interférences doivent exister dans les deux sens, i.e. si [x]
   est dans la liste d'interférence de [y], alors [y] doit être dans la liste
   d'interférence de [x].

*)

let add_interf (rig : (reg, reg Set.t) Hashtbl.t) (x: reg) (y: reg) : unit =
    (* TODO *)
    ()


(* [make_interf_live rig live] ajoute des arcs dans le graphe d'interférence
   pour chaque paire de registres vivants en même temps à un point de programme.
   *)
let make_interf_live
    (rig: (reg, reg Set.t) Hashtbl.t)
    (live : (int, reg Set.t) Hashtbl.t) : unit =
    (* TODO *)
   ()

(* [build_interference_graph live_out] construit, en utilisant les fonctions que
   vous avez écrites, le graphe d'interférence en fonction de la vivacité des
   variables à la sortie des nœuds donné par [live_out].

   Offert par la maison !
*)
let build_interference_graph (live_out : (int, reg Set.t) Hashtbl.t) code : (reg, reg Set.t) Hashtbl.t  =
  let interf = Hashtbl.create 17 in
  (* On ajoute un sommet pour chaque variable qui apparaît dans le programme. *)
  Hashtbl.iter (fun _ s ->
      Set.iter (fun v -> Hashtbl.replace interf v Set.empty) s
    ) live_out;
  make_interf_live interf live_out;
(* Les registres dans lesquels on écrit mais qui ne sont jamais vivants doivent être considérés comme en interférence avec tous les autres. *)
  let written_regs = written_rtl_regs code in
  let written_regs_never_live =
    Hashtbl.fold (fun _ regset_live_together acc -> Set.diff acc regset_live_together) live_out
      written_regs in
  let other_regs = Hashtbl.keys interf |> Set.of_enum in
  Set.iter (fun r ->
      Set.iter (fun r_other ->
          add_interf interf r r_other
        ) other_regs
    ) written_regs_never_live;
  interf

(* [remove_from_rig rig v] supprime le sommet [v] du graphe d'interférences
   [rig]. *)
let remove_from_rig (rig : (reg, reg Set.t) Hashtbl.t)  (v: reg) : unit =
   (* TODO *)
   ()


(* Type représentant les différentes décisions qui peuvent être prises par
   l'allocateur de registres.

   - [Spill r] signifie que le pseudo-registre [r] sera évincé (spillé) sur la pile.
    
   - [NoSpill r] signifie que le pseudo-registre [r] sera alloué dans un vrai
   registre physique.
*)
type regalloc_decision =
    Spill of reg
  | NoSpill of reg

(* Rappel de l'algorithme d'empilement des registres *)
(* Une fois le graphe d'interférences construit, il nous faut parcourir ce
   graphe afin de le colorer, avec [n] couleurs. On construit une pile de
   [regalloc_decision].

   Tant que le graphe n'est pas vide:

   - choisir un sommet [s] avec strictement moins de [n] voisins (ce sera le
   travail de la fonction [pick_node_with_fewer_than_n_neighbors]), empiler la
   décision [NoSpill s] et retirer [s] du graphe.

   - si aucun tel sommet n'existe dans le graphe, choisir un sommet [s]
   correspondant à un registre que l'on évincera (ce sera le travail de la
   fonction [pick_spilling_candidate]). Empiler la décision [Spill s] et retirer
   [s] du graphe.

*)

(* [pick_node_with_fewer_than_n_neighbors rig n] choisit un nœud du graphe [rig]
   possédant strictement moins de [n] voisins. Retourne [None] si aucun sommet
   ne satisfait cette condition. *)
let pick_node_with_fewer_than_n_neighbors (rig : (reg, reg Set.t) Hashtbl.t) (n: int) : reg option =
   (* TODO *)
   None

(* Lorsque la fonction précédente échoue (i.e. aucun sommet n'a moins de [n]
   voisins), on choisit un pseudo-registre à évincer.

   Une heuristique possible consiste à évincer le pseudo-registre qui a le plus
   de voisins dans le graphe [rig].

   [pick_spilling_candidate rig] retourne donc le pseudo-registre [r] qui a le
   plus de voisins dans [rig], ou [None] si [rig] est vide. *)
let pick_spilling_candidate (rig : (reg, reg Set.t) Hashtbl.t)  : reg option =
   (* TODO *)
   None

(* [make_stack rig stack ncolors] construit la pile, selon l'algorithme vu en
   cours (slide 26 du cours "Allocation de registres"
   présent sur Edunao.) *)
let rec make_stack (rig : (reg, reg Set.t) Hashtbl.t)  (stack : regalloc_decision list) (ncolors: int) : regalloc_decision list =
   (* TODO *)
   stack

(* Maintenant que nous avons une pile de [regalloc_decision], il est temps de
   colorer notre graphe, i.e. associer une couleur (un numéro de registre
   physique) à chaque pseudo-registre. Nous allons parcourir la pile et pour
   chaque décision :

   -  [Spill r] : associer un emplacement sur la pile au pseudo-registre [r]. On
   choisira l'emplacement [next_stack_slot].

   - [NoSpill r] : associer une couleur (un registre) physique au
   pseudo-registre [r]. On choisira une couleur qui n'est pas déjà associée à un
   voisin de [r] dans [rig].

   Cette fonction prend en entrée :

   - [allocation] : l'allocation courante, que l'on mettra à jour, et qui
   permettra de trouver les couleurs qui ne sont pas déjà associées à des
   voisins.

   - [rig] : le graphe d'interférence, qui permettra de connaître les voisins
   d'un registre.

   - [all_colors] : l'ensemble des couleurs que l'on peut allouer.

   - [next_stack_slot] : le prochain emplacement disponible sur la pile. Cela
   représentera des offsets négatifs par rapport à fp, on le mettra donc à jour
   en décrémentant cette valeur de 1.
   - [decision] : une décision parmi celles empilées.

   Cette fonction met à jour [allocation] et renvoie la nouvelle valeur de
   [next_stack_slot].

*)
let allocate (allocation: (reg, loc) Hashtbl.t) (rig: (reg, reg Set.t) Hashtbl.t)
    (all_colors: int Set.t)
    (next_stack_slot: int) (decision: regalloc_decision)
  : int =
   (* TODO *)
   next_stack_slot

(* [regalloc_fun f live_out all_colors] effectue l'allocation de registres pour
   la fonction [f].

   - [live_out] est un mapping des numéros d'instructions dans la fonction
   Linear vers l'ensemble des registres vivants après cette instruction.

   - [all_colors] est l'ensemble des registres que l'on pourra utiliser.

   Cette fonction renvoie un triplet [(rig, allocation, next_stack_slot)] :

   - [rig] est le graphe d'interférences (simplement pour l'affichage)

   - [allocation] est l'allocation de registre que vous aurez construit

   - [next_stack_slot] est le prochain emplacement disponible sur la pile
   (utilisé dans [ltl_gen], qui vous est fourni.)
*)
let regalloc_fun (f: linear_fun)
    (live_out: (int, reg Set.t) Hashtbl.t)
    (all_colors: int Set.t) :
  (reg, reg Set.t) Hashtbl.t      (* the RIG *)
  * (reg, loc) Hashtbl.t          (* the allocation *)
  * int                         (* the next stack slot *)
  =
  let rig = build_interference_graph live_out f.linearfunbody in

  let allocation = Hashtbl.create 17 in
  (* Les pseudo-registres qui contiennent les arguments sont traités séparément
     dans [ltl_gen.ml]. On les enlève donc du graphe. *)
  List.iter (fun p -> remove_from_rig rig p) f.linearfunargs;
  (* On effectue une copie [g] du graphe d'interférence [rig]. En effet, comme
     on va supprimer des sommets du graphe, on perd l'information
     d'interférence, dont on aura besoin pour effectuer la coloration. *)
  let g = Hashtbl.copy rig in
  let stack = make_stack g [] (Set.cardinal all_colors) in
  let next_stack_slot =
    List.fold_left (fun next_stack_slot decision ->
        allocate allocation rig all_colors next_stack_slot decision
      ) (-1) stack in
  (rig, allocation, next_stack_slot)


(* [dump_interf_graph fname rig] affiche les interférences associées à chaque
   registre. Peut être utile pour le débogage ! Pas besoin d'inspecter cette
   fonction, à moins qu'elle soit buggée... :-) *)
let dump_interf_graph oc (fname, rig, allocation) =
  let colors = Array.of_list [
      "blue"; "red"; "orange"; "pink"; "green"; "purple";
      "brown"; "turquoise"; "gray"; "gold"; "darkorchid"; "bisque";
      "darkseagreen"; "cornsilk"; "burlywood"; "dodgerblue"; "antiquewhite"; "firebrick";
      "deepskyblue"; "darkolivegreen"; "hotpink"; "lightsalmon"; "magenta"; "lawngreen";
    ] in
  let color_of_allocation r =
    match Hashtbl.find_option allocation r with
    | Some (Reg r) ->
      Array.get colors (r mod Array.length colors)
    | _ -> "white"
  in
  Format.fprintf oc "subgraph cluster_%s{\n" fname;
  Format.fprintf oc "label=\"%s\";\n" fname;
  Hashtbl.keys rig |> Enum.iter (fun r ->
      Format.fprintf oc "%s_r%d [label=\"r%d\",style=filled,fillcolor=\"%s\"];\n" fname r r (color_of_allocation r)
    );
  Hashtbl.iter
    (fun i s ->
       Set.iter (fun x ->
           Format.fprintf oc "%s_r%d -> %s_r%d;\n" fname i fname x
         ) s;)
    rig;
  Format.fprintf oc "}\n"

let dump_interf_graphs oc allocations =
  Format.fprintf oc "digraph RIGS {\n";
  Hashtbl.iter (fun fname (rig, allocation, next_stack_slot) ->
      dump_interf_graph oc (fname, rig, allocation)
    ) allocations;
  Format.fprintf oc "}\n"

(* On applique l'allocation de registres à tout le programme Linear, et on
   affiche tout ça dans le rapport (la page HTML de chaque fichier). *)
let regalloc lp lives all_colors =
  let allocations = Hashtbl.create 17 in
  List.iter (function (fname,Gfun f) ->
      begin match Hashtbl.find_option lives fname with
      | Some (live_in, live_out) ->
        let (rig, allocation, curstackslot) =
          if !Options.naive_regalloc
          then let (al, nss) = regalloc_on_stack_fun f in
            (Hashtbl.create 0, al, nss)
          else regalloc_fun f live_out all_colors
        in
        Hashtbl.replace allocations fname (rig, allocation, curstackslot)
      | None -> ()
      end
    ) lp;
  dump !Options.rig_dump dump_interf_graphs allocations
    (call_dot "regalloc" "Register Allocation");
  allocations