Techniques et outils pour la compilation


précédentsommairesuivant

I. Introduction

Dans le sens le plus usuel du terme, la compilation est une transformation que l'on fait subir à un programme écrit dans un langage évolué pour le rendre exécutable. Fondamentalement, c'est une traduction : un texte écrit en Pascal, C, Java, etc., exprime un algorithme et il s'agit de produire un autre texte, spécifiant le même algorithme dans le langage d'une machine que nous cherchons à programmer.

En généralisant un peu, on peut dire que compiler c'est lire une suite de caractères obéissant à une certaine syntaxe, en construisant une (autre) représentation de l'information que ces caractères expriment. De ce point de vue, beaucoup d'opérations apparaissent comme étant de la compilation ; à la limite, la lecture d'un nombre, qu'on obtient en C par une instruction comme :

 
Sélectionnez
scanf("%f", &x);

est déjà de la compilation, puisqu'il s'agit de lire des caractères constituant l'écriture d'une valeur selon la syntaxe des nombres décimaux et de fabriquer une autre représentation de la même information, à savoir sa valeur numérique.

Bien sûr, les questions qui nous intéresseront ici seront plus complexes que la simple lecture d'un nombre.

Mais il faut comprendre que le domaine d'application des principes et méthodes de l'écriture de compilateurs contient bien d'autres choses que la seule production de programmes exécutables. Chaque fois que vous aurez à écrire un programme lisant des expressions plus compliquées que des nombres, vous pourrez tirer profit des concepts, techniques et outils expliqués dans ce cours.

I-A. Structure de principe d'un compilateur

La nature de ce qui sort d'un compilateur est très variable. Cela peut être un programme exécutable pour un processeur physique, comme un Pentium III ou un G4, ou un fichier de code pour une machine virtuelle, comme la machine Java, ou un code abstrait destiné à un outil qui en fera ultérieurement du code exécutable, ou encore le codage d'un arbre représentant la structure logique d'un programme, etc.

En entrée d'un compilateur on trouve toujours la même chose : une suite de caractères, appelée le texte source(1). Voici les phases en lesquelles se décompose le travail d'un compilateur, du moins d'un point de vue logique(2) (voyez la figure 1) :

Analyse lexicale Dans cette phase, les caractères isolés qui constituent le texte source sont regroupés pour former des unités lexicales, qui sont les mots du langage.

L'analyse lexicale opère sous le contrôle de l'analyse syntaxique ; elle apparaît comme une sorte de fonction de « lecture améliorée », qui fournit un mot lors de chaque appel.

Analyse syntaxique Alors que l'analyse lexicale reconnaît les mots du langage, l'analyse syntaxique en reconnaît les phrases. Le rôle principal de cette phase est de dire si le texte source appartient au langage considéré, c'est-à-dire s'il est correct relativement à la grammaire de ce dernier.

Analyse sémantique La structure du texte source étant correcte, il s'agit ici de vérifier certaines propriétés sémantiques, c'est-à-dire relatives à la signification de la phrase et de ses constituants :

  • les identificateurs apparaissant dans les expressions ont-ils été déclarés ?
  • les opérandes ont-ils les types requis par les opérateurs ?
  • les opérandes sont-ils compatibles ? N'y a-t-il pas des conversions à insérer ?
  • les arguments des appels de fonctions ont-ils le nombre et le type requis ?
  • etc.

Génération de code intermédiaire Après les phases d'analyse, certains compilateurs ne produisent pas directement le code attendu en sortie, mais une représentation intermédiaire, une sorte de code pour une machine abstraite. Cela permet de concevoir indépendamment les premières phases du compilateur (constituant ce que l'on appelle sa face avant) qui ne dépendent que du langage source compilé et les dernières phases (formant sa face arrière) qui ne dépendent que du langage cible ; l'idéal serait d'avoir plusieurs faces avant et plusieurs faces arrière qu'on pourrait assembler librement(3).

Image non disponible
Fig. 1 - Phases logiques de la compilation d'une instruction

Optimisation du code Il s'agit généralement ici de transformer le code afin que le programme résultant s'exécute plus rapidement. Par exemple :

  • détecter l'inutilité de recalculer des expressions dont la valeur est déjà connue ;
  • transporter à l'extérieur des boucles des expressions et sous-expressions dont les opérandes ont la même valeur à toutes les itérations ;
  • détecter, et supprimer, les expressions inutiles ;
  • etc.

Génération du code final Cette phase, la plus impressionnante pour le néophyte, n'est pas forcément la plus difficile à réaliser. Elle nécessite la connaissance de la machine cible (réelle, virtuelle ou abstraite), et notamment de ses possibilités en matière de registres, piles, etc.


précédentsommairesuivant
Conseil : le texte source a probablement été composé à l'aide d'un éditeur de texte qui le montre sous forme de pages faites de plusieurs lignes mais, pour ce que nous avons à en faire ici, prenez l'habitude de l'imaginer comme s'il était écrit sur un long et mince ruban, formant une seule ligne.
C'est une organisation logique ; en pratique, certaines de ces phases peuvent être imbriquées, et d'autres absentes.
De la sorte, avec n faces avant pour n langages source et m faces arrière correspondant à m machines cibles, on disposerait automatiquement de n x m compilateurs distincts. Mais cela reste, pour le moment, un fantasme d'informaticien.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2001 Henri Garreta. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.