Allocation de registres

Voici le cours correspondant. La solution est ici.

Présentation

Les sources sont dans l’archive td8-coloring.tar.gz, on construit l’exécutable avec la commande make.

L’allocation de registres est un des modules les plus importants du compilateur, parce que la qualité du code produit dépendra beaucoup de la qualité de l’allocation de registres. On cherche donc à minimiser le nombre de registres sauvegardés en pile (code plus efficace en temps), et à utiliser pour ceux-ci un nombre minimum d’emplacements de pile (code plus efficace en espace).

Cela conduit à plusieurs types de coloriages différents, pour lesquels vous allez implémenter successivement des algorithmes de plus en plus efficaces (et de plus en plus complexes).

Vous allez compléter les squelettes de code donnés dans coloring.ml et spill.ml afin de satisfaire les spécifications correspondantes coloring.mli et spill.mli, qui définissent :

(* dans coloring.mli *)
type decision =
  | Spill
  | Color of MIPS.register

(* dans spill.mli *)
type decision =
    MIPSOps.offset

(* dans les deux cas *)
type coloring =
    decision Interference.Vertex.Map.t

Une decision associée à une variable (ou pseudo-registre) v correspond à un choix :

  1. lors de l’allocation de registres : Spill si v est sauvegardée en pile et Color $r si v est sauvegardée dans le registre physique $r.
  2. lors de l’allocation d’emplacements de pile : l’offset auquel la variable v sera sauvegardée en pile.

Un coloring est une table d’association qui associe les variables du programme à des decision.

Dans la suite, le degré d’une variable est le nombre d’arêtes d’interférences liées à cette variable.

Le but du TD est de construire des coloring appropriés.

Différents coloriages

On s’intéresse d’abord à l’allocation de registres physiques, puis à l’allocation d’emplacements de pile.

Vous aurez besoin des fonctions définies dans interference.mli.

Avec une boîte de crayons de couleurs

Vous allez compléter le code de coloring.ml. Lors de l’allocation de registres, on est limité par le nombre de registres disponibles, ici MIPS.allocatable. On applique donc un coloriage glouton sur un ensemble de couleurs fini.

colorier (graph) =
  choisir une variable v avec le degré minimum
  allouer (graph, v)

allouer (graph, v)
  colorier (graph \ v)
  déterminer les couleurs possibles pour v
  si aucune couleur possible
    alors 
      allouer v sur la pile
    sinon 
      choisir une couleur c pour v

Vous aurez besoin des fonctions iph, ipp, remove, lowest et choose du module Interference. On vous demande d’implémenter les fonctions suivantes, où la fonction simplification correspond à colorier dans l’algorithme, et la fonction selection correspond à allouer.

let forbidden_colors (graph : Interference.graph) (coloring : coloring) (v : Register.t) : ColorSet.t = ...

let simplification (graph : Interference.graph) : coloring = ...

let selection (graph : Interference.graph) (v : Vertex.t) : coloring = ...

Voir la solution.

En 16 millions de couleurs

Vous allez compléter le code de spill.ml. Lors de l’allocation d’emplacements de pile, on peut considérer que le nombre d’emplacements possibles est infini. Les variables avec de nombreuses interférences ne posent pas de problème ici. Cela nous amène à optimiser l’allocation d’emplacements de pile pour tenir compte des préférences entre variables. On applique donc un coloriage avec fusion sur un ensemble de couleurs infini.

fusionner (graph) =
  s'il existe une arête de préférence (u,v) du graphe
  alors 
    fusionner (graph | u et v ont fusionné)
  sinon
    colorier (graph)

colorier (graph) =
  choisir une variable v avec le degré minimum
  colorier (graph \ v)
  déterminer les couleurs impossibles pour v
  choisir une couleur c pour v

Vous aurez besoin des fonctions ipp, remove, lowest, pppick et coalesce du module Interference. Attention à la fonction coalesce qui rend un graphe où ne reste qu’un seul des 2 noeuds qui lui sont passés en argument (le deuxième). Cela impose de colorier également le premier après coup. On vous demande d’implémenter les fonctions suivantes, où la fonction simplify correspond à colorier dans l’algorithme, et la fonction coalesce correspond à fusionner.

let forbidden_slots (graph : Interference.graph) (coloring : coloring) (v : Register.t) : Int32Set.t = ...

let simplify (graph : Interference.graph) : coloring = ...

let coalesce (graph : Interference.graph) : coloring = ...

Voir la solution.

Vous avez implémenté complètement l’allocation de registres !

Vous pouvez désormais tester votre programme avec make ltl.

Du coloriage à l’art pictural

De nombreuses optimisations existent pour l’allocation de registres, on va en implémenter quelques-unes. On ne s’intéresse ici qu’à l’allocation de registres physiques du module Coloring.

La fusion conservative

On voudrait pouvoir allouer les variables reliées par une arête de préférence au même registre physique. Comme on n’a pas ici un nombre de couleurs infini, il faut procéder plus finement que lors de l’allocation d’emplacements de pile. On applique donc un coloriage avec fusion conservative sur un ensemble de couleurs fini.

colorier (graph) =
  s'il existe une variable v de degré inférieur à k
  alors 
    allouer (graph, v)
  sinon
    fusionner (graph)

allouer (graph, v)
  colorier (graph \ v)
  déterminer les couleurs possibles pour v
  si aucune couleur possible
    alors 
      allouer v sur la pile
    sinon 
      choisir une couleur c pour v

fusionner (graph) =
  s'il existe une arête de préférence (u,v) du graphe
    qui passe le test de George
  alors 
    colorier (graph | u et v ont fusionné)
  sinon
    empiler (graph)

empiler (graph) =
  choisir une variable v de degré maximum
  allouer (graph, v)

Vous aurez besoin des fonctions pppick, coalesce, coalesceh et highest du module Interference. Les fonctions qui implémentent le test de George sont georgepp et georgeph. On vous demande d’implémenter ou de modifier les fonctions suivantes, où la fonction spilling correspond à empiler dans l’algorithme, et la fonction coalescing correspond à fusionner.

let simplification (graph : Interference.graph) : coloring = ...

let coalescing (graph : Interference.graph) : coloring = ...

let spilling (graph : Interference.graph) : coloring = ...

Voir la solution.

La fusion conservative avec congélation

Dans la version de la fusion conservative que vous avez implémentée, seules les variables de fort degré sont fusionnées. On aimerait pouvoir fusionner également des variables de faible degré reliées par une arête de préférence. On applique donc un coloriage avec fusion conservative et congélation sur un ensemble de couleurs fini.

colorier (graph) =
  s'il existe une variable v de degré inférieur à k
    et sans arête de préférence
  alors 
    allouer (graph, v)
  sinon
    fusionner (graph)

fusionner (graph) =
  s'il existe une arête de préférence (u,v) du graphe
    qui passe le test de George
  alors 
    colorier (graph | u et v ont fusionné)
  sinon
    congeler (graph)

congeler (graph) =
  s'il existe une variable v de degré inférieur à k
  alors
    enlever du graphe les arêtes de préférence liées à v
    colorier (graph)
  sinon
    empiler (graph)

Note : allouer et empiler sont conservés à l’identique.

Vous aurez besoin des fonctions lowest_non_move_related et lowest du module Interference. On vous demande d’implémenter ou de modifier les fonctions suivantes, où la fonction freezing correspond à congeler dans l’algorithme.

let simplification (graph : Interference.graph) : coloring = ...

let coalescing (graph : Interference.graph) : coloring = ...

let freezing (graph : Interference.graph) : coloring = ...

Voir la solution.

La fusion conservative avec congélation et coût

Jusqu’à maintenant, on a uniquement optimisé le nombre de variables qui doivent être allouées en pile. Comme un accès à la pile est plus coûteux qu’un accès à un registre, on aimerait aussi que les variables les plus utilisées soient allouées à des registres, même si cela entraîne un peu plus d’allocations en pile.

Pour cela on utilise une heuristique : on définit le coût d’une variable comme le rapport du nombre d’utilisations de cette variable sur son degré.

empiler (graph) =
  choisir une variable v avec le coût minimum
  allouer (graph, v)

Vous aurez besoin de la fonction minimum du module Interference. Le coût est donné par la fonction cost. Modifiez spilling pour utiliser ce coût.

Voir la solution.

La solution complète se trouve dans les fichiers coloring.ml et spill.ml.


Ce document a été traduit de LATEX par HEVEA