Compilation Génération de code Génération de code ● ● La partie la plus difficile de la compilation À partir du code intermédiaire, il faut générer de l'assembleur : – choisir les instructions – choisir où stocker les variables (registre ou mémoire) Choix d'instructions ● ● On choisit les instructions assembleurs utilisées, mais en laissant les noms de variables comme paramètres. Version simple : – pour chaque instruction ou nœud d'expression, on choisit l'instruction ou les instructions les plus rapides qui réalisent le calcul demandé (c'est là qu'on choisit entre décalage de bits et division par deux) – si ce choix dépend des valeurs, on peut essayer de voir si on a un indice sur les valeurs prises par les variables (indice de tableau, cas d'un switch, type énuméré) Choix d'instructions ● En pratique, une instruction d'assembleur effectue plusieurs nœuds à la fois : – ● ● leal 3(%eax,%ebx,4),%ecx : %ecx = %eax+4*%ebx+3 On cherche un recouvrement de poids minimum des arbres des expresions et des instructions du langage intermédiaire Comme ce problème est NP-complet, on doit choisir une heuristique, par exemple gloutonne. Attribution des registres ● ● ● Cette étape choisit quelles variables on va stocker dans un registre. Le reste sera stocké en mémoire Pour cela, on détermine la zone de vie des variables (rien à voir avec le scope), c'est-à-dire la portion de code pendant laquelle la variable doit être mémorisée La zone commence à l'affectation de la variable, et termine à sa dernière utilisation Graphe de flot d'instructions ● Le graphe de flot d'instructions est un graphe orienté dont les nœuds sont les instructions, et qui relie les instructions qui peuvent se suivre pendant l'exécution du programme : – une flèche entre deux instructions qui se suivent quand la première n'est pas un saut inconditionnel – une entre entre un instruction de saut (conditionnel ou non) et sa destination Calcul de la zone de vie ● ● ● ● Pour calculer la zone de vie d'une variable x, on part de toutes les instructions qui lisent x, et propage la zone de vie en suivant les flêches en sens inverse On arrête la propagation aux instructions où x est affectée Quand la zone de vie n'est pas connexe, on considère que chaque composante connexe est une variable différente Quand une variable est copiée dans une autre, et que les zones de vie sont disjointes, on peut considérer que c'est la même. Graphe d'incompatibilité ● ● ● ● Deux variables sont incompatibles quand leurs zones de vie se recouvrent On dessine un graphe d'incompatibilité, dont les nœuds sont les variables, et qui relie les variables incompatibles On effectue un coloriage du graphe, et à chaque couleur correspond un registre (encore un problème NP‑complet) S'il n'y a pas assez de registres, il faut choisir de stocker les variables les moins utilisées (attention aux boucles) en mémoire. Exemple function f(X,Y) if X != 0 goto L1 N=5 goto L2 L1: N=Y L2: L=1 I=N+1 L3: T=2*N if T >= I goto L4 L=L*I I=I+1 goto L3 L4: U="Ici\n" U="Ici2\n" call printf(U) call printf(U) I=2 T=2*N L5: T=N-I U=L if T==0 goto L6 call display(W,T,U) L=L/I I=I+1 goto L5 L6: T=2*N U=40*L W=alloc_byte_array T U call fill(0,1,N,W)